Create a script to import W3C tests
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 4 May 2013 02:07:30 +0000 (02:07 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 4 May 2013 02:07:30 +0000 (02:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=111513

Patch by Rebecca Hauck <rhauck@adobe.com> on 2013-05-03
Reviewed by Dirk Pranke.

Script to automate the import of W3C tests into WebKit.
For the full description of how it works, see the comments
at the top of test_importer.py.

* Scripts/import-w3c-tests: Added.
* Scripts/webkitpy/w3c/__init__.py: Added.
* Scripts/webkitpy/w3c/test_converter.py: Added.
(TestConverter):
(TestConverter.__init__):
(TestConverter.load_prefixed_prop_list):
(TestConverter.load_file):
(TestConverter.convert_for_webkit):
(TestConverter.convert_testharness_paths):
(TestConverter.convert_prefixed_properties):
(TestConverter.scrub_unprefixed_props):
(TestConverter.replace_tag):
* Scripts/webkitpy/w3c/test_converter_unittest.py: Added.
(TestConverterTest):
(TestConverterTest.testLoadPrefixedPropList):
(TestConverterTest.test_convertForWebkitNothingToConvert):
(test_convertForWebkitHarnessOnly):
(test_convertForWebkitPropsOnly):
(test_convertForWebkitHarnessAndProps):
(test_convertTestHarnessPaths):
(test_convertPrefixedProperties):
(verifyTestHarnessPaths):
(verifyPrefixedProperties):
(generateTestContent):
* Scripts/webkitpy/w3c/test_importer.py: Added.
(main):
(parse_args):
(validate_import_directory):
(TestImporter):
(TestImporter.__init__):
(TestImporter.do_import):
(TestImporter.get_changeset):
(TestImporter.scan_source_directory):
(TestImporter.import_tests):
(TestImporter.setup_destination_directory):
(TestImporter.get_test_status):
(TestImporter.remove_deleted_files):
(TestImporter.write_import_log):
* Scripts/webkitpy/w3c/test_importer_unittest.py: Added.
(TestImporterTest):
(TestImporterTest.test_ImportDirWithNoTests):
* Scripts/webkitpy/w3c/test_parser.py: Added.
(TestParser):
(TestParser.__init__):
(TestParser.load_file):
(TestParser.analyze_test):
(TestParser.get_reftests):
(TestParser.is_jstest):
(TestParser.get_support_files):
* Scripts/webkitpy/w3c/test_parser_unittest.py: Added.
(TestParserTest):
(TestParserTest.test_analyzeTestReftestOneMatch):
(test_analyzeTestReftestMultipleMatches):
(test_analyzeTestReftestMatchAndMismatch):
(test_analyzeTestReftestWithRefSupportFiles):
(test_analyzeJSTest):
(test_analyzePixelTestAllTrue):
(test_analyzePixelTestAllFalse):
(test_analyzeNonHTMLFile):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@149547 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/import-w3c-tests [new file with mode: 0755]
Tools/Scripts/webkitpy/w3c/__init__.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_converter.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_converter_unittest.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_importer.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_importer_unittest.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_parser.py [new file with mode: 0644]
Tools/Scripts/webkitpy/w3c/test_parser_unittest.py [new file with mode: 0644]

index ce4f0bbf0ab25d1c68eb400607911abd0504152f..3c18969532b3388ac0bf0f8a0ac53db5aa329d36 100644 (file)
@@ -1,3 +1,74 @@
+2013-05-03  Rebecca Hauck  <rhauck@adobe.com>
+
+        Create a script to import W3C tests
+        https://bugs.webkit.org/show_bug.cgi?id=111513
+
+        Reviewed by Dirk Pranke.
+
+        Script to automate the import of W3C tests into WebKit. 
+        For the full description of how it works, see the comments
+        at the top of test_importer.py.
+
+        * Scripts/import-w3c-tests: Added.
+        * Scripts/webkitpy/w3c/__init__.py: Added.
+        * Scripts/webkitpy/w3c/test_converter.py: Added.
+        (TestConverter):
+        (TestConverter.__init__):
+        (TestConverter.load_prefixed_prop_list):
+        (TestConverter.load_file):
+        (TestConverter.convert_for_webkit):
+        (TestConverter.convert_testharness_paths):
+        (TestConverter.convert_prefixed_properties):
+        (TestConverter.scrub_unprefixed_props):
+        (TestConverter.replace_tag):
+        * Scripts/webkitpy/w3c/test_converter_unittest.py: Added.
+        (TestConverterTest):
+        (TestConverterTest.testLoadPrefixedPropList):
+        (TestConverterTest.test_convertForWebkitNothingToConvert):
+        (test_convertForWebkitHarnessOnly):
+        (test_convertForWebkitPropsOnly):
+        (test_convertForWebkitHarnessAndProps):
+        (test_convertTestHarnessPaths):
+        (test_convertPrefixedProperties):
+        (verifyTestHarnessPaths):
+        (verifyPrefixedProperties):
+        (generateTestContent):
+        * Scripts/webkitpy/w3c/test_importer.py: Added.
+        (main):
+        (parse_args):
+        (validate_import_directory):
+        (TestImporter):
+        (TestImporter.__init__):
+        (TestImporter.do_import):
+        (TestImporter.get_changeset):
+        (TestImporter.scan_source_directory):
+        (TestImporter.import_tests):
+        (TestImporter.setup_destination_directory):
+        (TestImporter.get_test_status):
+        (TestImporter.remove_deleted_files):
+        (TestImporter.write_import_log):
+        * Scripts/webkitpy/w3c/test_importer_unittest.py: Added.
+        (TestImporterTest):
+        (TestImporterTest.test_ImportDirWithNoTests):
+        * Scripts/webkitpy/w3c/test_parser.py: Added.
+        (TestParser):
+        (TestParser.__init__):
+        (TestParser.load_file):
+        (TestParser.analyze_test):
+        (TestParser.get_reftests):
+        (TestParser.is_jstest):
+        (TestParser.get_support_files):
+        * Scripts/webkitpy/w3c/test_parser_unittest.py: Added.
+        (TestParserTest):
+        (TestParserTest.test_analyzeTestReftestOneMatch):
+        (test_analyzeTestReftestMultipleMatches):
+        (test_analyzeTestReftestMatchAndMismatch):
+        (test_analyzeTestReftestWithRefSupportFiles):
+        (test_analyzeJSTest):
+        (test_analyzePixelTestAllTrue):
+        (test_analyzePixelTestAllFalse):
+        (test_analyzeNonHTMLFile):
+
 2013-05-03  Christophe Dumez  <ch.dumez@sisa.samsung.com>
 
         Unreviewed. Update Viatcheslav Ostapenko's email in contributors.json.
diff --git a/Tools/Scripts/import-w3c-tests b/Tools/Scripts/import-w3c-tests
new file mode 100755 (executable)
index 0000000..bb72096
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+import sys
+
+from webkitpy.w3c import test_importer
+
+
+sys.exit(test_importer.main(sys.argv[1:], sys.stdout, sys.stderr))
diff --git a/Tools/Scripts/webkitpy/w3c/__init__.py b/Tools/Scripts/webkitpy/w3c/__init__.py
new file mode 100644 (file)
index 0000000..ef65bee
--- /dev/null
@@ -0,0 +1 @@
+# Required for Python to search this directory for module files
diff --git a/Tools/Scripts/webkitpy/w3c/test_converter.py b/Tools/Scripts/webkitpy/w3c/test_converter.py
new file mode 100644 (file)
index 0000000..d18fa21
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+import re
+import os
+
+from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, Tag
+
+SOURCE_SUBDIR = 'Source'
+WEBCORE_SUBDIR = 'WebCore'
+CSS_SUBDIR = 'css'
+CSS_PROPERTY_NAMES_IN = 'CSSPropertyNames.in'
+LAYOUT_TESTS_DIRECTORY = 'LayoutTests'
+RESOURCES_DIRECTORY = 'resources'
+WEBKIT_ROOT = __file__.split(os.path.sep + 'Tools')[0]
+
+
+class TestConverter(object):
+
+    def __init__(self):
+        self.prefixed_properties = self.load_prefixed_prop_list()
+
+    def load_prefixed_prop_list(self):
+        """ Reads the current list of CSS properties requiring the -webkit- prefix from Source/WebCore/CSS/CSSPropertyNames.in and stores them in an instance variable """
+
+        prefixed_prop_list = []
+
+        # Open CSSPropertyNames.in to get the current list of things requiring prefixes
+        f = open(os.path.join(os.path.sep, WEBKIT_ROOT, SOURCE_SUBDIR, WEBCORE_SUBDIR,
+                                           CSS_SUBDIR, CSS_PROPERTY_NAMES_IN))
+        lines = f.readlines()
+        for line in lines:
+            # Look for anything with the -webkit- prefix
+            match = re.search('^-webkit-[\w|-]*', line)
+            if match is not None:
+                # Ignore the ones where both the prefixed and non-prefixed property
+                # are supported - denoted by -webkit-some-property = some-property
+                if len(line.split('=')) == 2 and \
+                   line.split('=')[1].strip() in line.split('=')[0].strip():
+                    continue
+                else:
+                    prefixed_prop_list.append(match.group(0))
+
+        return prefixed_prop_list
+
+    def load_file(self, filename):
+        """ Opens the test file |filename| to convert """
+
+        f = open(filename)
+        contents = f.read()
+        f.close()
+
+        return contents
+
+    def convert_for_webkit(self, new_path, contents=None, filename=None):
+        """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit. Returns the list of prefixed CSS properties and the modified contents."""
+
+        if contents is None and filename is None:
+            return None
+
+        if contents is None:
+            contents = self.load_file(filename)
+
+        # Handle plain old CSS files
+        if filename.endswith('.css'):
+            return self.scrub_unprefixed_props(contents)
+
+        # Otherwise, parse it as if HTML
+        doc = BeautifulSoup(contents)
+        jstest = self.convert_testharness_paths(doc, new_path)
+        converted = self.convert_prefixed_properties(doc)
+
+        if len(converted[0]) > 0 or jstest is True:
+            return converted
+        else:
+            return None
+
+    def convert_testharness_paths(self, doc, new_path):
+        """ Update links to testharness.js in the BeautifulSoup |doc| to point to the copy in |new_path|. Returns whether the document was modified."""
+
+        # Look for the W3C-style path to any testharness files - scripts (.js) or links (.css)
+        pattern = re.compile('/resources/testharness')
+        script_tags = doc.findAll(src=pattern)
+        link_tags = doc.findAll(href=pattern)
+        testharness_tags = script_tags + link_tags
+
+        if len(testharness_tags) != 0:
+
+            # Construct the relative path from the new directory to LayoutTests/resources
+            resources_path = os.path.join(os.path.sep, WEBKIT_ROOT, LAYOUT_TESTS_DIRECTORY, RESOURCES_DIRECTORY)
+            resources_relpath = os.path.relpath(resources_path, new_path)
+
+            for tag in testharness_tags:
+                # Build a new tag with the correct path
+                attr = 'src'
+                if tag.name != 'script':
+                    attr = 'href'
+
+                # Build a new tag with the correct relative path
+                old_path = tag[attr]
+                new_tag = Tag(doc, tag.name, tag.attrs)
+                new_tag[attr] = re.sub(pattern, resources_relpath + '/testharness', old_path)
+
+                # Update the doc with the new tag
+                self.replace_tag(doc, tag, new_tag)
+
+            return True
+        else:
+            return False
+
+    def convert_prefixed_properties(self, doc):
+        """ Searches a BeautifulSoup |doc| for any CSS properties requiring the -webkit- prefix and converts them. Returns the list of converted properties and the modified document as a string """
+
+        converted_props = []
+
+        # Look for inline and document styles
+        inline_styles = doc.findAll(style=re.compile('.*'))
+        style_tags = doc.findAll('style')
+        all_styles = inline_styles + style_tags
+
+        for tag in all_styles:
+
+            # Get the text whether in a style tag or style attribute
+            style_text = ''
+            if tag.name == 'style':
+                if len(tag.contents) == 0:
+                    continue
+                else:
+                    style_text = tag.contents[0]
+            else:
+                style_text = tag['style']
+
+            # Scrub it for props requiring prefixes
+            scrubbed_style = self.scrub_unprefixed_props(style_text)
+
+            # Rewrite tag only if changes were made
+            if len(scrubbed_style[0]) > 0:
+                converted_props.extend(scrubbed_style[0])
+
+                # Build a new tag with the modified text
+                new_tag = Tag(doc, tag.name, tag.attrs)
+                new_tag.insert(0, scrubbed_style[1])
+
+                # Update the doc with the new tag
+                self.replace_tag(doc, tag, new_tag)
+
+        return (converted_props, doc.prettify())
+
+    def scrub_unprefixed_props(self, text):
+        """ Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix. Returns the list of converted properties and the modified string """
+
+        converted_props = []
+
+        # Spin through the whole list of prefixed properties
+        for prefixed_prop in self.prefixed_properties:
+
+            # Pull the prefix off
+            unprefixed_prop = prefixed_prop.replace('-webkit-', '')
+
+            # Look for the various ways it might be in the CSS
+            # Match the the property preceded by either whitespace or left curly brace
+            # or at the beginning of the string (for inline style attribute)
+            pattern = '([\s{]|^)' + unprefixed_prop + '(\s+:|:)'
+            if re.search(pattern, text):
+                print 'converting ' + unprefixed_prop + ' -> ' + prefixed_prop
+                converted_props.append(prefixed_prop)
+                text = re.sub(pattern, prefixed_prop + ':', text)
+
+        # TODO: Handle the JS versions of these properties, too.
+
+        return (converted_props, text)
+
+    def replace_tag(self, doc, old_tag, new_tag):
+        """ Inserts a |new_tag| into a BeautifulSoup |doc| and removes the |old_tag| """
+
+        # Replace the previous tag with the new one
+        index = old_tag.parent.contents.index(old_tag)
+        old_tag.parent.insert(index, new_tag)
+        old_tag.extract()
diff --git a/Tools/Scripts/webkitpy/w3c/test_converter_unittest.py b/Tools/Scripts/webkitpy/w3c/test_converter_unittest.py
new file mode 100644 (file)
index 0000000..b9883b0
--- /dev/null
@@ -0,0 +1,361 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+import os
+from random import randint
+import re
+import unittest2 as unittest
+
+from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
+
+from webkitpy.w3c.test_converter import TestConverter
+from webkitpy.w3c import test_converter
+
+
+class TestConverterTest(unittest.TestCase):
+
+    def testLoadPrefixedPropList(self):
+        """ Tests that the current list of properties requiring the -webkit- prefix load correctly """
+
+        # Get the full list of prefixed properties
+        converter = TestConverter()
+        prop_list = converter.prefixed_properties
+
+        # Verify we got properties back
+        self.assertGreater(len(prop_list), 0, 'No prefixed properties found')
+
+        # Verify they're all prefixed
+        for prop in prop_list:
+            self.assertTrue(prop.startswith('-webkit-'))
+
+    def test_convertForWebkitNothingToConvert(self):
+        """ Tests convert_for_webkit() using a basic test that has nothing to convert """
+
+        test_html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>CSS Test: DESCRIPTION OF TEST</title>
+<link rel="author" title="NAME_OF_AUTHOR"
+href="mailto:EMAIL OR http://CONTACT_PAGE"/>
+<link rel="help" href="RELEVANT_SPEC_SECTION"/>
+<meta name="assert" content="TEST ASSERTION"/>
+<style type="text/css"><![CDATA[
+CSS FOR TEST
+]]></style>
+</head>
+<body>
+CONTENT OF TEST
+</body>
+</html>
+"""
+        converter = TestConverter()
+
+        # Try to convert the html
+        converted = converter.convert_for_webkit('/nothing/to/convert', contents=test_html, filename='somefile.html')
+
+        # Verify nothing was converted
+        self.assertEqual(converted, None, 'test was should not have been converted')
+
+    def test_convertForWebkitHarnessOnly(self):
+        """ Tests convert_for_webkit() using a basic JS test that uses testharness.js only and has no prefixed properties """
+
+        test_html = """<head>
+<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
+<script src="/resources/testharness.js"></script>
+</head>
+"""
+        converter = TestConverter()
+
+        # Create path to a fake test directory
+        test_path = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                              test_converter.LAYOUT_TESTS_DIRECTORY,
+                                              test_converter.CSS_SUBDIR,
+                                              'harnessonly')
+
+        # Convert the html
+        converted = converter.convert_for_webkit(test_path, contents=test_html, filename='somefile.html')
+
+        # Verify a conversion happened
+        self.assertNotEqual(converted, None, 'test was not converted')
+
+        # Verify that both the harness paths were converted correctly
+        self.verifyTestHarnessPaths(converted[1], test_path, 1, 1)
+
+        # Verify no properties were converted
+        self.verifyPrefixedProperties(converted, [])
+
+    def test_convertForWebkitPropsOnly(self):
+        """ Tests convert_for_webkit() using a test that has 2 prefixed properties: 1 in a style block + 1 inline style """
+
+        test_html = """<html>
+<head>
+<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
+<script src="/resources/testharness.js"></script>
+<style type="text/css">
+
+#block1 { @test0@: propvalue; }
+
+</style>
+</head>
+<body>
+<div id="elem1" style="@test1@: propvalue;"></div>
+</body>
+</html>
+"""
+
+        converter = TestConverter()
+
+        # Create path to a fake test directory
+        test_path = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                              test_converter.LAYOUT_TESTS_DIRECTORY,
+                                              test_converter.CSS_SUBDIR,
+                                              'harnessandprops')
+
+        # Generate & insert the test properties into the test_html
+        test_content = self.generateTestContent(converter.prefixed_properties, 2, test_html)
+
+        # Convert the html
+        converted = converter.convert_for_webkit(test_path, contents=test_content[1], filename='somefile.html')
+
+        # Verify a conversion happened
+        self.assertNotEqual(converted, None, 'test was not converted')
+
+        # Verify that both the harness paths and properties were all converted correctly
+        self.verifyTestHarnessPaths(converted[1], test_path, 1, 1)
+        self.verifyPrefixedProperties(converted, test_content[0])
+
+    def test_convertForWebkitHarnessAndProps(self):
+        """ 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 """
+
+        test_html = """<html>
+<head>
+<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
+<script src="/resources/testharness.js"></script>
+<style type="text/css">
+
+#block1 { @test0@: propvalue; }
+#block2 { @test1@: propvalue; }
+#block3 { @test2@: propvalue; }
+
+</style>
+</head>
+<body>
+<div id="elem1" style="@test3@: propvalue;"></div>
+</body>
+</html>
+"""
+        converter = TestConverter()
+
+        # Create path to a fake test directory
+        test_path = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                              test_converter.LAYOUT_TESTS_DIRECTORY,
+                                              test_converter.CSS_SUBDIR,
+                                              'harnessandprops')
+
+        # Generate & insert the test properties into the test_html
+        test_content = self.generateTestContent(converter.prefixed_properties, 4, test_html)
+
+        # Convert the html
+        converted = converter.convert_for_webkit(test_path, contents=test_content[1], filename='somefile.html')
+
+        # Verify a conversion happened
+        self.assertNotEqual(converted, None, 'test was not converted')
+
+        # Verify that both the harness paths and properties were all converted correctly
+        self.verifyTestHarnessPaths(converted[1], test_path, 1, 1)
+        self.verifyPrefixedProperties(converted, test_content[0])
+
+    def test_convertTestHarnessPaths(self):
+        """ Tests convert_testharness_paths() with a test that uses all three testharness files """
+
+        # Basic test content using all three testharness files
+        test_html = """<head>
+<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+"""
+        converter = TestConverter()
+
+        # Create path to a fake test directory
+        test_path = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                              test_converter.LAYOUT_TESTS_DIRECTORY,
+                                              test_converter.CSS_SUBDIR,
+                                              'testharnesspaths')
+
+        # Convert the html
+        doc = BeautifulSoup(test_html)
+        converted = converter.convert_testharness_paths(doc, test_path)
+
+        # Verify a conversion happened
+        self.assertTrue(converted, 'test was not converted')
+
+        # Verify all got converted correctly
+        self.verifyTestHarnessPaths(doc, test_path, 2, 1)
+
+    def test_convertPrefixedProperties(self):
+        """ Tests convert_prefixed_properties() file that has 20 properties requiring the -webkit- prefix:
+        10 in one style block + 5 in another style
+        block + 5 inline styles, including one with multiple prefixed properties.
+        The properties in the test content are in all sorts of wack formatting.
+        """
+
+        test_html = """<html>
+<style type="text/css"><![CDATA[
+
+.block1 {
+    width: 300px;
+    height: 300px
+}
+
+.block2 {
+    @test0@: propvalue;
+}
+
+.block3{@test1@: propvalue;}
+
+.block4 { @test2@:propvalue; }
+
+.block5{ @test3@ :propvalue; }
+
+#block6 {    @test4@   :   propvalue;  }
+
+#block7
+{
+    @test5@: propvalue;
+}
+
+#block8 { @test6@: propvalue; }
+
+#block9:pseudo
+{
+
+    @test7@: propvalue;
+    @test8@:  propvalue propvalue propvalue;;
+}
+
+]]></style>
+</head>
+<body>
+    <div id="elem1" style="@test9@: propvalue;"></div>
+    <div id="elem2" style="propname: propvalue; @test10@ : propvalue; propname:propvalue;"></div>
+    <div id="elem2" style="@test11@: propvalue; @test12@ : propvalue; @test13@   :propvalue;"></div>
+    <div id="elem3" style="@test14@:propvalue"></div>
+</body>
+<style type="text/css"><![CDATA[
+
+.block10{ @test15@: propvalue; }
+.block11{ @test16@: propvalue; }
+.block12{ @test17@: propvalue; }
+#block13:pseudo
+{
+    @test18@: propvalue;
+    @test19@: propvalue;
+}
+
+]]></style>
+</html>
+"""
+        converter = TestConverter()
+
+        # Generate & insert the test properties into the test_html
+        test_content = self.generateTestContent(converter.prefixed_properties, 20, test_html)
+
+        # Convert the html
+        converted = converter.convert_prefixed_properties(BeautifulSoup(test_content[1]))
+
+        # Verify a conversion happened
+        self.assertNotEqual(converted, None, 'test was not converted')
+
+        # Verify all the generated test properties were converted correctly
+        self.verifyPrefixedProperties(converted, test_content[0])
+
+    def verifyTestHarnessPaths(self, converted, test_path, num_src_paths, num_href_paths):
+        """ Verifies all W3C-style paths to test harness files were converted correctly """
+
+        # Make soup if we need to
+        if isinstance(converted, basestring):
+            converted = BeautifulSoup(converted)
+
+        # Build the path to LayoutTests/resources
+        resources_dir = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                                  test_converter.LAYOUT_TESTS_DIRECTORY,
+                                                  test_converter.RESOURCES_DIRECTORY)
+
+        # Verify the original paths are no longer there
+        orig_path_pattern = re.compile('\"/resources/testharness')
+        self.assertEquals(len(converted.findAll(src=orig_path_pattern)), 0,
+                          'testharness src path was not converted')
+        self.assertEquals(len(converted.findAll(href=orig_path_pattern)), 0,
+                          'testharness href path was not converted')
+
+        # Get the new relpath from the tester dir
+        new_relpath = os.path.relpath(resources_dir, test_path)
+
+        # Verify it's in all the right places
+        relpath_pattern = re.compile(new_relpath)
+        self.assertEquals(len(converted.findAll(src=relpath_pattern)), num_src_paths,
+                          'testharness src relative path not correct')
+        self.assertEquals(len(converted.findAll(href=relpath_pattern)), num_href_paths,
+                          'testharness href relative path not correct')
+
+    def verifyPrefixedProperties(self, converted, test_properties):
+        """ Verifies a list of test_properties were converted correctly """
+
+        # Verify that the number of test properties equals the number that were converted
+        self.assertEqual(len(converted[0]), len(test_properties), 'Incorrect number of properties converted')
+
+        # Verify they're all in the converted document
+        for test_prop in test_properties:
+            self.assertTrue((test_prop in converted[1]), 'Property ' + test_prop + ' not found in converted doc')
+
+    def generateTestContent(self, full_prop_list, num_test_properties, html):
+        """ Generates a random list of unique properties requiring the -webkit- prefix and inserts them into html content that's been tokenized using \'@testXX@\' syntax """
+
+        test_properties = []
+
+        # Grab a random bunch of 20 unique properties requiring prefixes to test with
+        count = 0
+        while count < num_test_properties:
+            idx = randint(0, len(full_prop_list) - 1)
+            if not(full_prop_list[idx] in test_properties):
+                test_properties.append(full_prop_list[idx])
+                count += 1
+
+        # Replace the tokens in the testhtml with the test properties. Walk backward
+        # through the list to replace the double-digit tokens first
+        idx = len(test_properties) - 1
+        while idx >= 0:
+            # Use the unprefixed version
+            test_prop = test_properties[idx].replace('-webkit-', '')
+            # Replace the token
+            html = html.replace('@test' + str(idx) + '@', test_prop)
+            idx -= 1
+
+        return (test_properties, html)
diff --git a/Tools/Scripts/webkitpy/w3c/test_importer.py b/Tools/Scripts/webkitpy/w3c/test_importer.py
new file mode 100644 (file)
index 0000000..5541722
--- /dev/null
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+ This script imports a directory of W3C CSS tests into WebKit.
+
+ You must have checked out the W3C repository to your local drive.
+
+ This script will import the tests into WebKit following these rules:
+
+    - Only tests that are approved or officially submitted awaiting review are imported
+
+    - All tests are imported into LayoutTests/csswg
+
+    - If the tests are approved, they'll be imported into a directory tree that
+      mirrors the CSS Mercurial repo. For example, <csswg_repo_root>/approved/css2.1 is brought in
+      as LayoutTests/csswg/approved/css2.1, maintaining the entire directory structure under that
+
+    - If the tests are submitted, they'll be brought in as LayoutTests/csswg/submitted and will also
+      maintain their directory structure under that. For example, everything under
+      <csswg_repo_root>/contributors/adobe/submitted is brought into submitted, mirroring its
+      directory structure in the csswg repo
+
+    - If the import directory specified is just a contributor folder, only the submitted folder
+      for that contributor is brought in. For example, to import all of Mozilla's tests, either
+      <csswg_repo_root>/contributors/mozilla or <csswg_repo_root>/contributors/mozilla/submitted
+      will work and are equivalent
+
+    - For the time being, this script won't work if you try to import the full set of submitted
+      tests under contributors/*/submitted. Since these are awaiting review, this is just a small
+      control mechanism to enforce carefully selecting what non-approved tests are imported.
+      It can obviously and easily be changed.
+
+    - By default, only reftests and jstest are imported. This can be overridden with a -a or --all
+      argument
+
+    - Also by default, if test files by the same name already exist in the destination directory,
+      they are overwritten with the idea that running this script would refresh files periodically.
+      This can also be overridden by a -n or --no-overwrite flag
+
+    - All files are converted to work in WebKit:
+         1. .xht extensions are changed to .xhtml to make new-run-webkit-tests happy
+         2. Paths to testharness.js files are modified point to Webkit's copy of them in
+            LayoutTests/resources, using the correct relative path from the new location
+         3. All CSS properties requiring the -webkit-vendor prefix are prefixed - this current
+            list of what needs prefixes is read from Source/WebCore/CSS/CSSProperties.in
+         4. Each reftest has its own copy of its reference file following the naming conventions
+            new-run-webkit-tests expects
+         5. If a a reference files lives outside the directory of the test that uses it, it is checked
+            for paths to support files as it will be imported into a different relative position to the
+            test file (in the same directory)
+
+     - Upon completion, script outputs the total number tests imported, broken down by test type
+
+     - Also upon completion, each directory where files are imported will have w3c-import.log written
+       with a timestamp, the W3C Mercurial changeset if available, the list of CSS properties used that
+       require prefixes, the list of imported files, and guidance for future test modification and
+       maintenance.
+
+     - On subsequent imports, this file is read to determine if files have been removed in the newer changesets.
+       The script removes these files accordingly.
+"""
+import datetime
+import mimetypes
+from optparse import OptionParser
+import os
+import shutil
+import subprocess
+import sys
+
+from webkitpy.w3c.test_parser import TestParser
+from webkitpy.w3c.test_converter import TestConverter
+
+LAYOUT_TESTS_DIRECTORY = 'LayoutTests'
+CSSWG_DIRECTORY = 'csswg'
+RESOURCES_DIRECTORY = 'resources'
+APPROVED = 'approved'
+SUBMITTED = 'submitted'
+UNKNOWN = 'unknown'
+NOT_AVAILABLE = 'Not Available'
+
+
+def main(argv, _, stderr):
+
+    parse_args()
+    import_dir = validate_import_directory()
+    test_importer = TestImporter(import_dir, options)
+    test_importer.do_import()
+
+
+def parse_args():
+
+    global options
+    global args
+
+    parser = OptionParser(usage='usage: %prog [options] w3c_test_directory')
+    parser.add_option('-n', '--no-overwrite',
+                      #action='store_true',
+                      default=False,
+                      help='Flag to prevent duplicate test files from overwriting existing tests. By default, they will be overwritten')
+    parser.add_option('-a', '--all',
+                      action='store_true',
+                      default=False,
+                      help='Import all tests including reftests, JS tests, and manual/pixel tests. By default, only reftests and JS tests are imported')
+
+    (options, args) = parser.parse_args()
+
+    if len(args) != 1:
+        parser.error('Incorrect number of arguments')
+
+
+def validate_import_directory():
+
+    import_dir = args[0]
+
+    # Make sure the w3c test directory exists
+    if not os.path.exists(import_dir):
+        sys.exit('Source directory %s not found!' % import_dir)
+
+    # Make sure the tests are officially submitted to the W3C, either approved or
+    # submitted following their directory naming conventions
+    if import_dir.find(APPROVED) == -1 and \
+       import_dir.find(SUBMITTED) == -1:
+        # If not pointed directly to the approved directory or to any submitted
+        # directory, check for a submitted subdirectory and go with that
+        import_dir = os.path.join(import_dir, 'submitted')
+        if not os.path.exists(os.path.join(import_dir)):
+            sys.exit('Unable to import tests that aren\'t approved or submitted to the W3C')
+
+    return import_dir
+
+
+class TestImporter(object):
+
+    def __init__(self, source_directory, options):
+        self.options = options
+
+        self.source_directory = source_directory
+        self.webkit_root = __file__.split(os.path.sep + 'Tools')[0]
+        self.destination_directory = os.path.join(os.path.sep, self.webkit_root, LAYOUT_TESTS_DIRECTORY, CSSWG_DIRECTORY)
+
+        self.changeset = NOT_AVAILABLE
+        self.test_status = UNKNOWN
+
+        self.import_list = []
+
+    def do_import(self):
+        self.scan_source_directory(self.source_directory)
+        self.get_changeset()
+        self.import_tests()
+
+    def get_changeset(self):
+        """ Runs hg tip to get the current changeset and parses the output to get the changeset. If that doesn't workout for some reason, it just becomes 'Not Available' """
+
+        try:
+            # Assuming Mercurial is set up on the system, try to grab the changeset we're importing
+            proc = subprocess.Popen(['hg', 'tip'], env=os.environ, stdout=subprocess.PIPE, cwd=self.source_directory)
+            self.changeset = proc.stdout.readlines()[0]
+            self.changeset = self.changeset.split('changeset:')[1]
+        except:
+            # Oh well, we tried
+            self.changeset = 'Not Available'
+
+    def scan_source_directory(self, directory):
+        """ Walks the |directory| looking for HTML files that are importable tests. """
+
+        for root, dirs, files in os.walk(directory):
+
+            print 'Scanning ' + root + '...'
+            total_tests = 0
+            reftests = 0
+            jstests = 0
+
+            # Ignore any repo stuff
+            if '.git' in dirs:
+                dirs.remove('.git')
+            if '.hg' in dirs:
+                dirs.remove('.hg')
+            # archive and data dirs are internal csswg things that live in every approved directory
+            if 'data' in dirs:
+                dirs.remove('data')
+            if 'archive' in dirs:
+                dirs.remove('archive')
+
+            copy_list = []
+
+            for filename in files:
+
+                fullpath = os.path.join(root, filename)
+
+                # Only html or xml are considered tests
+                mimetype = mimetypes.guess_type(fullpath)
+                if 'html' in str(mimetype[0]) or 'xml' in str(mimetype[0]):
+
+                    test_parser = TestParser(vars(options), filename=fullpath)
+                    test_info = test_parser.analyze_test()
+
+                    if test_info is None:
+                        continue
+
+                    if 'reference' in test_info.keys():
+
+                        reftests += 1
+                        total_tests += 1
+                        test_basename = os.path.basename(test_info['test'])
+
+                        # Add the ref file, renaming it to Webkit's way
+                        # Note: The preferable way to handle this would be to just
+                        #       add support in Webkit's harness to read metadata
+                        #       rather than rely on a file naming convention. Since
+                        #       the CSSWG tests support many:one tests:reference,
+                        #       enabling metadata parsing in our harness would also
+                        #       reduce the number of files copied in. As this is
+                        #       implemented here, we are knowingly importing multiple
+                        #       copies of the same ref files to adhere to Webkit's way
+                        ref_file = os.path.splitext(test_basename)[0] + '-expected'
+                        ref_file += os.path.splitext(test_basename)[1]
+
+                        copy_list.append({'src': test_info['reference'], 'dest': ref_file})
+                        copy_list.append({'src': test_info['test'], 'dest': filename})
+
+                        # If there are ref support files, the destination is should now be relative to the
+                        # test file and new location of the ref file
+                        if 'refsupport' in test_info.keys():
+                            for support_file in test_info['refsupport']:
+                                # Build the correct source path
+                                source_file = os.path.join(os.path.dirname(test_info['reference']), support_file)
+                                source_file = os.path.normpath(source_file)
+                                # Keep the dest as it was
+                                to_copy = {'src': source_file, 'dest': support_file}
+                                # Only add it once
+                                if not(to_copy in copy_list):
+                                    copy_list.append(to_copy)
+
+                    elif 'jstest' in test_info.keys():
+                        jstests += 1
+                        total_tests += 1
+                        copy_list.append({'src': fullpath, 'dest': filename})
+
+                    else:
+                        total_tests += 1
+                        copy_list.append({'src': fullpath, 'dest': filename})
+
+                # Ignore dotfiles and random perl scripts that are in some dirs
+                elif not(filename.startswith('.')) and not(filename.endswith('.pl')):
+                    copy_list.append({'src': fullpath, 'dest': filename})
+
+            if total_tests == 0:
+                # If there are no tests in this directory, skip the support dir that lives in all
+                # of the 'approved' folders as part of the automated build system
+                if 'support' in dirs:
+                    dirs.remove('support')
+
+                if len(copy_list) > 0:
+                    # Only add this directory to the list if there's something to import
+                    self.import_list.append({'dirname': root, 'copy_list': copy_list,
+                                            'reftests': reftests, 'jstests': jstests, 'total_tests': total_tests})
+
+    def import_tests(self):
+        """ Copies and converts the full list of importable tests into Webkit and logs what was imported in each directory """
+
+        # Only set up the destination directory if there's something to import
+        if len(self.import_list) > 0:
+            self.setup_destination_directory()
+
+        converter = TestConverter()
+        total_imported_tests = 0
+        total_imported_reftests = 0
+        total_imported_jstests = 0
+
+        for dir_to_copy in self.import_list:
+
+            total_imported_tests += dir_to_copy['total_tests']
+            total_imported_reftests += dir_to_copy['reftests']
+            total_imported_jstests += dir_to_copy['jstests']
+
+            prefixed_properties = []
+
+            if len(dir_to_copy['copy_list']) > 0:
+
+                # Build the subpath starting with the approved/submitted directory
+                orig_path = dir_to_copy['dirname']
+                start = orig_path.find(self.test_status)
+                new_subpath = orig_path[start:len(orig_path)]
+
+                # Append the new subpath to the destination_directory
+                new_path = os.path.join(self.destination_directory, new_subpath)
+
+                # Create the destination subdirectories if not there
+                if not(os.path.exists(new_path)):
+                    os.makedirs(new_path)
+
+                copied_files = []
+
+                for file_to_copy in dir_to_copy['copy_list']:
+
+                    orig_filepath = os.path.normpath(file_to_copy['src'])
+
+                    # Shouldn't be any directories on the list, but check to be safe
+                    if os.path.isdir(orig_filepath):
+                        continue
+
+                    # Don't choke if the file can't be found
+                    if not(os.path.exists(orig_filepath)):
+                        print 'Warning: ' + orig_filepath + ' not found. Possible error in the test.'
+                        continue
+
+                    # Append the correct destination filename
+                    new_filepath = os.path.join(new_path, file_to_copy['dest'])
+
+                    # Change extension to work in the WebKit harness
+                    new_filepath = new_filepath.replace('.xht', '.xhtml')
+
+                    # Make the directories needed
+                    if not(os.path.exists(os.path.dirname(new_filepath))):
+                        os.makedirs(os.path.dirname(new_filepath))
+
+                    # Don't overwrite existing tests if specified
+                    if options.no_overwrite is True and os.path.exists(new_filepath):
+                        print 'Skipping import of existing file ' + new_filepath
+                    else:
+                        # TODO: Maybe doing a file diff is in order here for existing files?
+                        #       In other words, there's no sense in overwriting identical files, but
+                        #       there's no harm in copying the identical thing.
+                        print 'Importing: ' + orig_filepath
+                        print '       As: ' + new_filepath
+
+                    # Only html, xml, or css should be converted
+                    # TODO: Eventually, so should js when support is added for this type of conversion
+                    mimetype = mimetypes.guess_type(orig_filepath)
+                    if 'html' in str(mimetype[0]) or \
+                       'xml' in str(mimetype[0])  or \
+                       'css' in str(mimetype[0]):
+
+                        # Convert for WebKit
+                        converted_file = converter.convert_for_webkit(new_path, filename=orig_filepath)
+
+                        if converted_file is None:
+                            # Straight copy if nothing's changed
+                            shutil.copyfile(orig_filepath, new_filepath)
+                        else:
+                            # Write out the converted test
+                            prefixed_properties.extend(set(converted_file[0]) - set(prefixed_properties))
+                            outfile = open(new_filepath, 'w')
+                            outfile.write(converted_file[1])
+                            outfile.close()
+                    else:
+                        shutil.copyfile(orig_filepath, new_filepath)
+
+                    copied_files.append(new_filepath.replace(self.webkit_root, ''))
+
+                # Take care of anything that may have been removed since the last import
+                self.remove_deleted_files(new_path, copied_files)
+
+                # Drop some info about what just happened here
+                self.write_import_log(new_path, copied_files, prefixed_properties)
+
+        print 'Import complete'
+
+        print 'IMPORTED ' + str(total_imported_tests) + ' TOTAL TESTS'
+        print 'Imported ' + str(total_imported_reftests) + ' reftests'
+        print 'Imported ' + str(total_imported_jstests) + ' JS tests'
+        print 'Imported ' + str(total_imported_tests - total_imported_jstests - total_imported_reftests) + ' pixel/manual tests'
+
+    def setup_destination_directory(self):
+        """ Creates a destination directory that mirrors that of the source approved or submitted directory """
+
+        # The test status is in the path, so grab it
+        self.get_test_status()
+
+        # Mirror the directory structure in the csswg repo under approved/ or submitted/
+        start = self.source_directory.find(self.test_status)
+        new_subpath = self.source_directory[start:len(self.source_directory)]
+
+        destination_directory = os.path.join(self.destination_directory, new_subpath)
+
+        if not os.path.exists(destination_directory):
+            os.makedirs(destination_directory)
+
+        print 'Tests will be imported into: ' + destination_directory
+
+    def get_test_status(self):
+        """ Sets the test status to either 'approved' or 'submitted' """
+
+        status = UNKNOWN
+
+        if self.source_directory.find(APPROVED) != -1:
+            status = APPROVED
+        elif self.source_directory.find(SUBMITTED) != -1:
+            status = SUBMITTED
+
+        self.test_status = status
+
+    def remove_deleted_files(self, import_directory, new_file_list):
+        """ Reads an import log in |import_directory| and compares it to the |new_file_list| and removes files that are not in the new list """
+
+        previous_file_list = []
+
+        # First check if there was a previous import here - there should be a log
+        import_log_file = os.path.join(import_directory, 'w3c-import.log')
+        if not(os.path.exists(import_log_file)):
+            return
+
+        import_log = open(import_log_file, 'r')
+        contents = import_log.readlines()
+
+        # Pull log the list of files from the previous import
+        if 'List of files\n' in contents:
+            list_idx = contents.index('List of files:\n') + 1
+            previous_file_list = contents[list_idx:]
+            previous_file_list = map(lambda s: s.strip(), previous_file_list)
+
+        deleted_files = set(previous_file_list) - set(new_file_list)
+
+        # Loop through and remove them all
+        for deleted_file in deleted_files:
+            print 'Deleting file removed from the W3C repo:' + deleted_file
+            deleted_file = os.path.join(self.webkit_root, deleted_file)
+            os.remove(deleted_file)
+
+        import_log.close()
+
+    def write_import_log(self, import_directory, file_list, prop_list):
+        """ Writes a w3c-import.log file in each directory with imported files. """
+
+        now = datetime.datetime.now()
+
+        # Create a log of this import with all sorts of good information
+        import_log = open(os.path.join(import_directory, 'w3c-import.log'), 'w')
+        import_log.write('The tests in this directory were imported from the W3C repository.\n')
+        import_log.write('Do NOT modify these tests directly in Webkit. Instead, push changes to the W3C CSS repo:\n\n')
+        import_log.write('http://hg.csswg.org/test\n\n')
+        import_log.write('Then run the Tools/Scripts/import-w3c-tests in Webkit to reimport\n\n')
+        import_log.write('Do NOT modify or remove this file\n\n')
+        import_log.write('------------------------------------------------------------------------\n')
+        import_log.write('Last Import: ' + now.strftime('%Y-%m-%d %H:%M') + '\n')
+        import_log.write('W3C Mercurial changeset: ' + self.changeset + '\n')
+        import_log.write('Test status at time of import: ' + self.test_status + '\n')
+        import_log.write('------------------------------------------------------------------------\n')
+        import_log.write('Properties requiring vendor prefixes:\n')
+        if len(prop_list) == 0:
+            import_log.write('None\n')
+        else:
+            for prop in prop_list:
+                import_log.write(prop + '\n')
+        import_log.write('------------------------------------------------------------------------\n')
+        import_log.write('List of files:\n')
+        for item in file_list:
+            import_log.write(item + '\n')
+
+        import_log.close()
diff --git a/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py b/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
new file mode 100644 (file)
index 0000000..d5809ed
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+import os
+import unittest2 as unittest
+
+from webkitpy.w3c import test_converter
+from webkitpy.w3c.test_importer import TestImporter
+
+WEBKIT_ROOT = __file__.split(os.path.sep + 'Tools')[0]
+
+
+class TestImporterTest(unittest.TestCase):
+
+    def test_ImportDirWithNoTests(self):
+        """ Tests do_import() with a directory that contains no tests """
+        dir_with_no_tests = os.path.join(os.path.sep, test_converter.WEBKIT_ROOT,
+                                                      test_converter.SOURCE_SUBDIR,
+                                                      test_converter.WEBCORE_SUBDIR,
+                                                      test_converter.CSS_SUBDIR)
+
+        importer = TestImporter(dir_with_no_tests, None)
+        importer.do_import()
+
+
+    # TODO: Need more tests, but what to use for valid source directory? Make it on the fly
+    #       and clean it up afterward?
diff --git a/Tools/Scripts/webkitpy/w3c/test_parser.py b/Tools/Scripts/webkitpy/w3c/test_parser.py
new file mode 100644 (file)
index 0000000..6e32558
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+import os
+import re
+
+from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup as Parser
+
+
+class TestParser(object):
+
+    def __init__(self, options, filename):
+        self.options = options
+        self.filename = filename
+        self.test_doc = self.load_file(filename)
+        self.ref_doc = None
+
+    def load_file(self, file_to_parse):
+        """ Opens and read the |file_to_parse| """
+        doc = None
+
+        if os.path.exists(file_to_parse):
+            f = open(file_to_parse)
+            html = f.read()
+            f.close()
+
+            doc = Parser(html)
+
+        return doc
+
+    def analyze_test(self, test_contents=None, ref_contents=None):
+        """ Analyzes a file to determine if it's a test, what type of test, and what reference or support files it requires. Returns all of the test info """
+
+        test_info = None
+
+        if test_contents is None and self.test_doc is None:
+            return test_info
+
+        if test_contents is not None:
+            self.test_doc = Parser(test_contents)
+
+        if ref_contents is not None:
+            self.ref_doc = Parser(ref_contents)
+
+        # First check if it's a reftest
+        matches = self.get_reftests(True)
+        if len(matches) != 0:
+
+            if len(matches) > 1:
+                print 'Warning: Webkit does not support multiple references. Importing the first ref defined in ' + os.path.basename(self.filename)
+
+            ref_file = os.path.join(os.path.dirname(self.filename), matches[0]['href'])
+            if self.ref_doc is None:
+                self.ref_doc = self.load_file(ref_file)
+
+            test_info = {'test': self.filename, 'reference': ref_file}
+
+            # If the ref file path is relative, we need to check it for relative paths
+            # because when it lands in Webkit, it will be moved down into the test dir
+            # Note: The test files themselves are not checked for support files
+            # that live outside their directories as it's the convention in the CSSWG
+            # to put all support files in the same dir or subdir as the test. All non-test
+            # files in the test's directory tree are automatically copied as part of the import as
+            # they are assumed to be required support files There is exactly one case in the entire
+            # css2.1 suite where at test depends on a file that lives in a different directory, which
+            # depends on another file that lives outside of its directory. This code covers that case :)
+            if matches[0]['href'].startswith('..'):
+                supportFiles = self.get_support_files(self.ref_doc)
+                test_info['refsupport'] = supportFiles
+
+        # Next, if it's a JS test
+        elif self.is_jstest():
+            test_info = {'test': self.filename, 'jstest': True}
+
+        # Or if we explicitly want everything
+        elif self.options['all'] is True and \
+             not('-ref' in self.filename) and \
+             not('reference' in self.filename):
+            test_info = {'test': self.filename}
+
+        # Courtesy check and warning if there are any mismatches
+        mismatches = self.get_reftests(False)
+        if len(mismatches) != 0:
+            print "Warning: Webkit does not support mismatch references. Ignoring mismatch defined in " + os.path.basename(self.filename)
+
+        return test_info
+
+    def get_reftests(self, find_match):
+        """ Searches for all reference links in the file. If |find_match| is searches for matches, otherwise for mismatches """
+
+        if find_match:
+            return self.test_doc.findAll(rel='match')
+        else:
+            return self.test_doc.findAll(rel='mismatch')
+
+    def is_jstest(self):
+        """ Searches the file for usage of W3C-style testharness paths """
+
+        if self.test_doc.find(src=re.compile('[\'\"/]?/resources/testharness')) is None:
+            return False
+        else:
+            return True
+
+    def get_support_files(self, doc):
+        """ Searches the file for all paths specified in url()'s, href or src attributes """
+
+        support_files = []
+
+        if doc is None:
+            return support_files
+
+        # Look for tags with src or href attributes
+        src_attrs = doc.findAll(src=re.compile('.*'))
+        href_attrs = doc.findAll(href=re.compile('.*'))
+
+        # Look for urls
+        url_pattern = re.compile('url\(.*\)')
+        urls = []
+        for url in doc.findAll(text=url_pattern):
+            url = re.search(url_pattern, url)
+            url = re.sub('url\([\'\"]', '', url.group(0))
+            url = re.sub('[\'\"]\)', '', url)
+            urls.append(url)
+
+        src_paths = [src_tag['src'] for src_tag in src_attrs]
+        href_paths = [href_tag['href'] for href_tag in href_attrs]
+
+        paths = src_paths + href_paths + urls
+        for path in paths:
+            # Ignore http and mailto links
+            if not(path.startswith('http:')) and \
+               not(path.startswith('mailto:')):
+                support_files.append(path)
+
+        return support_files
diff --git a/Tools/Scripts/webkitpy/w3c/test_parser_unittest.py b/Tools/Scripts/webkitpy/w3c/test_parser_unittest.py
new file mode 100644 (file)
index 0000000..8744cf4
--- /dev/null
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer.
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+import os
+import StringIO
+import sys
+import unittest2 as unittest
+
+from webkitpy.w3c.test_parser import TestParser
+
+
+options = {'all': False, 'no_overwrite': False}
+
+
+class TestParserTest(unittest.TestCase):
+
+    def test_analyzeTestReftestOneMatch(self):
+        """ Tests analyze_test() using a basic reftest with one match link and no mismatch links """
+
+        test_html = """<head>
+<link rel="match" href="green-box-ref.xht" />
+</head>
+"""
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'did not find a test')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertTrue('reference' in test_info.keys(), 'did not find a reference file')
+        self.assertTrue(test_info['reference'].startswith(test_path), 'reference path is not correct')
+        self.assertFalse('refsupport' in test_info.keys(), 'there should be no refsupport files for this test')
+        self.assertFalse('jstest' in test_info.keys(), 'test should not have been analyzed as a jstest')
+
+    def test_analyzeTestReftestMultipleMatches(self):
+        """ Tests analyze_test() using a reftest with multiple match links and no mismatch links """
+
+        test_html = """<head>
+<link rel="match" href="green-box-ref.xht" />
+<link rel="match" href="blue-box-ref.xht" />
+<link rel="match" href="orange-box-ref.xht" />
+</head>
+"""
+        # Capture stdout for this test
+        stdout = sys.stdout
+        output = StringIO.StringIO()
+        sys.stdout = output
+
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Restore stdout
+        sys.stdout = stdout
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'did not find a test')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertTrue('reference' in test_info.keys(), 'did not find a reference file')
+        self.assertTrue(test_info['reference'].startswith(test_path), 'reference path is not correct')
+        self.assertFalse('refsupport' in test_info.keys(), 'there should be no refsupport files for this test')
+        self.assertFalse('jstest' in test_info.keys(), 'test should not have been analyzed as a jstest')
+
+        # Verify we got the warning about multiple matches
+        self.assertTrue(output.getvalue().startswith('Warning'), 'no warning about multiple matches')
+
+    def test_analyzeTestReftestMatchAndMismatch(self):
+        """ Tests analyze_test() using a reftest with a one match link and one mismatch link """
+
+        test_html = """<head>
+<link rel="match" href="green-box-ref.xht" />
+<link rel="match" href="blue-box-ref.xht" />
+<link rel="mismatch" href="orange-box-notref.xht" />
+</head>
+"""
+        # Capture stdout for this test
+        stdout = sys.stdout
+        output = StringIO.StringIO()
+        sys.stdout = output
+
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Restore stdout
+        sys.stdout = stdout
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'did not find a test')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertTrue('reference' in test_info.keys(), 'did not find a reference file')
+        self.assertTrue(test_info['reference'].startswith(test_path), 'reference path is not correct')
+        self.assertFalse('refsupport' in test_info.keys(), 'there should be no refsupport files for this test')
+        self.assertFalse('jstest' in test_info.keys(), 'test should not have been analyzed as a jstest')
+
+        # Verify we got multiple warnings
+        warnings = output.getvalue().splitlines()
+        self.assertEquals(len(warnings), 2, 'all warnings were not displayed')
+        # Verify the both warnings mention the issue
+        self.assertTrue('multiple matches', 'no warning about multiple matches')
+        self.assertTrue('mismatch', 'no warning about mismatches')
+
+    def test_analyzeTestReftestWithRefSupportFiles(self):
+        """ Tests analyze_test() using a reftest that has refers to a reference file outside of the tests directory and the reference file has paths to other support files """
+
+        test_html = """<html>
+<head>
+<link rel="match" href="../reference/green-box-ref.xht" />
+</head>
+"""
+        ref_html = """<head>
+<link href="support/css/ref-stylesheet.css" rel="stylesheet" type="text/css">
+<style type="text/css">
+    background-image: url("../../support/some-image.png")
+</style>
+</head>
+<body>
+<div><img src="../support/black96x96.png" alt="Image download support must be enabled" /></div>
+</body>
+</html>
+"""
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html, ref_contents=ref_html)
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'did not find a test')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertTrue('reference' in test_info.keys(), 'did not find a reference file')
+        self.assertTrue(test_info['reference'].startswith(test_path), 'reference path is not correct')
+        self.assertTrue('refsupport' in test_info.keys(), 'there should be refsupport files for this test')
+        self.assertEquals(len(test_info['refsupport']), 3, 'there should be 3 support files in this reference')
+        self.assertFalse('jstest' in test_info.keys(), 'test should not have been analyzed as a jstest')
+
+    def test_analyzeJSTest(self):
+        """ Tests analyze_test() using a jstest """
+
+        test_html = """<head>
+<link href="/resources/testharness.css" rel="stylesheet" type="text/css">
+<script src="/resources/testharness.js"></script>
+</head>
+"""
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'test_info is None')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertFalse('reference' in test_info.keys(), 'shold not have found a reference file')
+        self.assertFalse('refsupport' in test_info.keys(), 'there should be no refsupport files for this test')
+        self.assertTrue('jstest' in test_info.keys(), 'test should be a jstest')
+
+    def test_analyzePixelTestAllTrue(self):
+        """ Tests analyze_test() using a test that is neither a reftest or jstest with all=False """
+
+        test_html = """<html>
+<head>
+<title>CSS Test: DESCRIPTION OF TEST</title>
+<link rel="author" title="NAME_OF_AUTHOR" />
+<style type="text/css"><![CDATA[
+CSS FOR TEST
+]]></style>
+</head>
+<body>
+CONTENT OF TEST
+</body>
+</html>
+"""
+        # Set options to 'all' so this gets found
+        options['all'] = True
+
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Verify the correct test info was found
+        self.assertNotEqual(test_info, None, 'test_info is None')
+        self.assertTrue('test' in test_info.keys(), 'did not find a test file')
+        self.assertFalse('reference' in test_info.keys(), 'shold not have found a reference file')
+        self.assertFalse('refsupport' in test_info.keys(), 'there should be no refsupport files for this test')
+        self.assertFalse('jstest' in test_info.keys(), 'test should not be a jstest')
+
+    def test_analyzePixelTestAllFalse(self):
+        """ Tests analyze_test() using a test that is neither a reftest or jstest, with -all=False """
+
+        test_html = """<html>
+<head>
+<title>CSS Test: DESCRIPTION OF TEST</title>
+<link rel="author" title="NAME_OF_AUTHOR" />
+<style type="text/css"><![CDATA[
+CSS FOR TEST
+]]></style>
+</head>
+<body>
+CONTENT OF TEST
+</body>
+</html>
+"""
+        # Set all to false so this gets skipped
+        options['all'] = False
+
+        # Analyze the test
+        test_path = '/some/madeup/path/'
+        parser = TestParser(options, test_path + 'somefile.html')
+        test_info = parser.analyze_test(test_contents=test_html)
+
+        # Verify the correct test info was found
+        self.assertEqual(test_info, None, 'test should have been skipped')
+
+    def test_analyzeNonHTMLFile(self):
+        """ Tests analyze_test() with a file this isn't html (this one!) """
+
+        # Try to analyze the script file we're testing (how meta)
+        test_parser_py_file = os.path.join(os.path.dirname(__file__), 'test_parser.py')
+
+        # Analyze the file
+        parser = TestParser(options, test_parser_py_file)
+        test_info = parser.analyze_test()
+
+        # Verify the correct test info was found
+        self.assertEqual(test_info, None, 'no tests should have been found in this file')