EWS should run JavaScriptCore tests
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Feb 2017 22:41:49 +0000 (22:41 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 17 Feb 2017 22:41:49 +0000 (22:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162458

Patch by Srinivasan Vijayaraghavan <svijayaraghavan@apple.com> on 2017-02-17
Reviewed by Alexey Proskuryakov.

* QueueStatusServer/config/queues.py: Add jsc-ews queue.
* QueueStatusServer/model/queuestatus.py:
(QueueStatus.did_skip): Returns whether patch was skipped, based on status.
* QueueStatusServer/handlers/statusbubble.py:
(StatusBubble._should_show_bubble_for): Add logic to hide jsc-ews bubble if the patch does not touch jsc.
* Scripts/webkitpy/common/checkout/scm/scm_mock.py:
(MockSCM.__init__): Add _mockChangedFiles variable.
(MockSCM.changed_files): Change to use _mockChangedFiles variables.
* Scripts/webkitpy/common/config/ews.json: Add config info for JSC EWS.
* Scripts/webkitpy/common/config/ports.py: Add support for JSC EWS in Mac Port.
(DeprecatedPort._append_build_style_flag): Helper function to append build_style to a command.
(DeprecatedPort.build_jsc_command): Added - command to build JSC only (quicker than building all of WebKit).
(DeprecatedPort.run_javascriptcore_tests_command): Allow JSC EWS to only run JSC tests.
(MacPort.run_webkit_tests_command): Check for JSC.
* Scripts/webkitpy/common/config/ports_mock.py:
(MockPort.run_javascriptcore_tests_command): Add build_style argument.
* Scripts/webkitpy/common/config/ports_unittest.py:
(DeprecatedPortTest.test_mac_port): Add unit tests for build-jsc shell command.
* Scripts/webkitpy/common/net/abstracttestresults.py: Added.
(AbstractTestResults): Abstract superclass of JSCTestResults and JSCTestResults.
(AbstractTestResults.test_results): Stub.
(AbstractTestResults.failing_tests): Stub.
(AbstractTestResults.did_exceed_test_failure_limit): Stub.
* Scripts/webkitpy/common/net/jsctestresults.py: Added.
(JSCTestResults): Added.
(JSCTestResults.intersection): Return a JSCTestResults object with failures common to both input objects.
(JSCTestResults.results_from_string): Use json library to parse results.
(JSCTestResults.__init__): Initialize members.
(JSCTestResults.equals): This enables unit testing.
(JSCTestResults.is_subset): Checks if one set of failures is a subset of another.
(JSCTestResults.all_passed): Getter.
(JSCTestResults.failing_tests): Getter.
(JSCTestResults.did_exceed_test_failure_limit): Getter.
* Scripts/webkitpy/common/net/jsctestresults_unittest.py: Added.
(JSCTestResultsTest): Class to test JSCTestResults.
(JSCTestResultsTest.test_results_from_string): Tests parsing.
(JSCTestResultsTest.test_intersection_api_tests): Unit test for intersection() class method.
(JSCTestResultsTest.test_intersection_stress_tests): Unit test for intersection() class method.
(JSCTestResultsTest.test_intersection_general_case): Unit test for intersection() class method.
* Scripts/webkitpy/port/base.py:
(Port.jsc_results_directory): Returns the directory for the JSC test results JSON file.
* Scripts/webkitpy/tool/bot/earlywarningsystemtask.py:
(EarlyWarningSystemTask.run): Add check_patch_relevance step.
* Scripts/webkitpy/tool/bot/jscews_unittest.py: Added.
(MockPatchAnalysisTask): Mocked-out version of PatchAnalysisTask that doesn't run commands.
(MockPatchAnalysisTask.__init__): Sets attributes.
(MockPatchAnalysisTask._test): Override to not run command.
(MockPatchAnalysisTask._build_and_test_without_patch): Override to not run command.
(MockPatchAnalysisTask.validate): Assume mocked patch is valid for purposes of testing retry logic.
(MockPatchAnalysisTask.test_run_count): Specific to the mocked version, to test retry logic.
(MockJSCEarlyWarningSystem): Mocked-out version of AbstarctEarlyWarningSystem so we can provide test results.
(MockJSCEarlyWarningSystem.__init__): Sets attributes, also sets group to jsc.
(MockJSCEarlyWarningSystem.test_results): Returns test results provided by us, instead of using a JSON reader.
(JSCEarlyWarningSystemTest): Class to test retry logic in below situations.
(JSCEarlyWarningSystemTest._create_task): Helper function to abstract out common code.
(JSCEarlyWarningSystemTest._results_indicate_all_passed): False if input is None or has failures, else True.
(JSCEarlyWarningSystemTest.test_success_case): Clean patch on clean tree.
(JSCEarlyWarningSystemTest.test_test_failure): Failed patch on clean tree.
(JSCEarlyWarningSystemTest.test_fix): Patch that fixes all tree redness.
(JSCEarlyWarningSystemTest.test_ineffective_patch): Patch that has same failures as tree.
(JSCEarlyWarningSystemTest.test_partially_effective_patch): Patch fixes some failures but adds no new failures.
(JSCEarlyWarningSystemTest.test_different_test_failures_in_patch_and_tree): Patch has some failures not in tree.
(JSCEarlyWarningSystemTest.test_first_results_could_not_be_read): Patch results not readable.
(JSCEarlyWarningSystemTest.test_second_results_could_not_be_read): Patch results not readable on second run.
(JSCEarlyWarningSystemTest.test_clean_results_could_not_be_read): Results from clean tree not readable.
(JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_pass): Patch has one flake and no failures.
(JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_pass_v2): Patch has one flake and no failures.
(JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_failure): Patch has flakes and failed tests.
(JSCEarlyWarningSystemTest.test_flaky_results_on_red_tree_pass): Patch has same failures as tree, plus a flake.
* Scripts/webkitpy/tool/bot/jsctestresultsreader.py: Added.
(JSCTestResultsReader): Reads results file.
(JSCTestResultsReader.__init__): Sets attributes.
(JSCTestResultsReader._read_file_contents): Reads file.
(JSCTestResultsReader.results): Reads the results file and returns a JSCTestResults object.
* Scripts/webkitpy/tool/bot/patchanalysistask.py:
(PatchIsNotApplicable): Exception for when patch doesn't have relevant changes.
(PatchAnalysisTask._check_patch_relevance): Added.
(PatchAnalysisTask._build): Check for JSC.
(PatchAnalysisTask._build_without_patch): Check for JSC.
(PatchAnalysisTask._test): Check for JSC.
(PatchAnalysisTask._build_and_test_without_patch): Check for JSC.
(PatchAnalysisTask._retry_jsc_tests): Retry logic for JSC EWS.
(PatchAnalysisTask._retry_layout_tests): Retry logic for layout tests EWS.
(PatchAnalysisTask._test_patch): Add retry logic for JSC.
* Scripts/webkitpy/tool/commands/download.py:
(CheckPatchRelevance): Add check-patch-relevance command.
* Scripts/webkitpy/tool/commands/earlywarningsystem.py:
(AbstractEarlyWarningSystem._create_task): Abstract out to enable mocking.
(AbstractEarlyWarningSystem.begin_work_queue): Use JSCTestResultsReader not LayoutTestResultsReader in JSC EWS.
(AbstractEarlyWarningSystem.review_patch): Handle PatchIsApplicable.
(AbstractEarlyWarningSystem.test_results): _layout_test_results_reader -> _test_results_reader.
(AbstractEarlyWarningSystem.archive_last_test_results): _layout_test_results_reader -> _test_results_reader.
(AbstractEarlyWarningSystem.group): This attribute determines the type of EWS (eg. JSC).
(AbstractEarlyWarningSystem.load_ews_classes): Add _group, and make classes of type cls to enable mocking.
* Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py:
(TestEWS): Sample layout test EWS class used for unit testing.
(TestJSCEWS): Sample JSC EWS class used for unit testing.
(AbstractEarlyWarningSystemTest.test_failing_tests_message.TestEWS): Add _group.
(AbstractEarlyWarningSystemTest.test_failing_jsc_tests_message.TestEWS): Added test for jsc failures message.
(AbstractEarlyWarningSystemTest): Add _group variable.
(EarlyWarningSystemTest._default_expected_logs): Add check-patch-relevance step, inconclusive logs, and group.
(EarlyWarningSystemTest._test_ews): Add logs_are_conclusive option to pass through to default_expected_logs().
(EarlyWarningSystemTest.test_inconclusive_test_results): Test not removing patch from queue if not conclusive.
(MockAbstractEarlyWarningSystemForInconclusiveJSCResults): Added.
(MockAbstractEarlyWarningSystemForInconclusiveJSCResults._test_patch): Simulates running tests but not getting a conclusive result.
(MockEarlyWarningSystemTaskForInconclusiveJSCResults): Added.
(MockEarlyWarningSystemTaskForInconclusiveJSCResults._create_task): Use MockEarlyWarningSystemTask (not EarlyWarningSystemTask).
* Scripts/webkitpy/tool/steps/__init__.py: Add CheckPatchRelevance import.
* Scripts/webkitpy/tool/steps/build.py:
(Build.options): Check for JSC.
(Build.build): Check for JSC.
(Build.run): Check for JSC.
* Scripts/webkitpy/tool/steps/checkpatchrelevance.py: Added.
(CheckPatchRelevance): Added.
(CheckPatchRelevance._does_contain_change_in_paths): Abstract function to perform regex matching.
(CheckPatchRelevance.run): Check if changed files in patch belong in certain folders.
* Scripts/webkitpy/tool/steps/options.py:
(Options): Add --group command line option.
* Scripts/webkitpy/tool/steps/runtests.py:
(RunTests.options): Add group.
(RunTests.run): Check for JSC.
(RunTests._run_webkit_tests): Check for JSC.
(RunTests): Add _group attribute.
(RunTests._run_javascriptcore_tests): New.
* Scripts/webkitpy/tool/steps/steps_unittest.py: Unit tests.

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

32 files changed:
Tools/ChangeLog
Tools/QueueStatusServer/config/messages.py
Tools/QueueStatusServer/config/queues.py
Tools/QueueStatusServer/handlers/statusbubble.py
Tools/QueueStatusServer/model/queues.py
Tools/QueueStatusServer/model/queuestatus.py
Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py
Tools/Scripts/webkitpy/common/config/ews.json
Tools/Scripts/webkitpy/common/config/ports.py
Tools/Scripts/webkitpy/common/config/ports_mock.py
Tools/Scripts/webkitpy/common/config/ports_unittest.py
Tools/Scripts/webkitpy/common/net/abstracttestresults.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/net/jsctestresults.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/net/jsctestresults_unittest.py [new file with mode: 0644]
Tools/Scripts/webkitpy/common/net/layouttestresults.py
Tools/Scripts/webkitpy/port/base.py
Tools/Scripts/webkitpy/tool/bot/earlywarningsystemtask.py
Tools/Scripts/webkitpy/tool/bot/jscews_unittest.py [new file with mode: 0644]
Tools/Scripts/webkitpy/tool/bot/jsctestresultsreader.py [new file with mode: 0644]
Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py
Tools/Scripts/webkitpy/tool/commands/download.py
Tools/Scripts/webkitpy/tool/commands/download_unittest.py
Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py
Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py
Tools/Scripts/webkitpy/tool/commands/queues.py
Tools/Scripts/webkitpy/tool/steps/__init__.py
Tools/Scripts/webkitpy/tool/steps/build.py
Tools/Scripts/webkitpy/tool/steps/checkpatchrelevance.py [new file with mode: 0644]
Tools/Scripts/webkitpy/tool/steps/options.py
Tools/Scripts/webkitpy/tool/steps/runtests.py
Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py
Tools/Scripts/webkitpy/tool/steps/steps_unittest.py

index b389372..a4d5af8 100644 (file)
@@ -1,3 +1,136 @@
+2017-02-17  Srinivasan Vijayaraghavan  <svijayaraghavan@apple.com>
+
+        EWS should run JavaScriptCore tests
+        https://bugs.webkit.org/show_bug.cgi?id=162458
+
+        Reviewed by Alexey Proskuryakov.
+
+        * QueueStatusServer/config/queues.py: Add jsc-ews queue.
+        * QueueStatusServer/model/queuestatus.py:
+        (QueueStatus.did_skip): Returns whether patch was skipped, based on status.
+        * QueueStatusServer/handlers/statusbubble.py:
+        (StatusBubble._should_show_bubble_for): Add logic to hide jsc-ews bubble if the patch does not touch jsc.
+        * Scripts/webkitpy/common/checkout/scm/scm_mock.py:
+        (MockSCM.__init__): Add _mockChangedFiles variable.
+        (MockSCM.changed_files): Change to use _mockChangedFiles variables.
+        * Scripts/webkitpy/common/config/ews.json: Add config info for JSC EWS.
+        * Scripts/webkitpy/common/config/ports.py: Add support for JSC EWS in Mac Port.
+        (DeprecatedPort._append_build_style_flag): Helper function to append build_style to a command.
+        (DeprecatedPort.build_jsc_command): Added - command to build JSC only (quicker than building all of WebKit).
+        (DeprecatedPort.run_javascriptcore_tests_command): Allow JSC EWS to only run JSC tests.
+        (MacPort.run_webkit_tests_command): Check for JSC.
+        * Scripts/webkitpy/common/config/ports_mock.py:
+        (MockPort.run_javascriptcore_tests_command): Add build_style argument.
+        * Scripts/webkitpy/common/config/ports_unittest.py:
+        (DeprecatedPortTest.test_mac_port): Add unit tests for build-jsc shell command.
+        * Scripts/webkitpy/common/net/abstracttestresults.py: Added.
+        (AbstractTestResults): Abstract superclass of JSCTestResults and JSCTestResults.
+        (AbstractTestResults.test_results): Stub.
+        (AbstractTestResults.failing_tests): Stub.
+        (AbstractTestResults.did_exceed_test_failure_limit): Stub.
+        * Scripts/webkitpy/common/net/jsctestresults.py: Added.
+        (JSCTestResults): Added.
+        (JSCTestResults.intersection): Return a JSCTestResults object with failures common to both input objects.
+        (JSCTestResults.results_from_string): Use json library to parse results.
+        (JSCTestResults.__init__): Initialize members.
+        (JSCTestResults.equals): This enables unit testing.
+        (JSCTestResults.is_subset): Checks if one set of failures is a subset of another.
+        (JSCTestResults.all_passed): Getter.
+        (JSCTestResults.failing_tests): Getter.
+        (JSCTestResults.did_exceed_test_failure_limit): Getter.
+        * Scripts/webkitpy/common/net/jsctestresults_unittest.py: Added.
+        (JSCTestResultsTest): Class to test JSCTestResults.
+        (JSCTestResultsTest.test_results_from_string): Tests parsing.
+        (JSCTestResultsTest.test_intersection_api_tests): Unit test for intersection() class method.
+        (JSCTestResultsTest.test_intersection_stress_tests): Unit test for intersection() class method.
+        (JSCTestResultsTest.test_intersection_general_case): Unit test for intersection() class method.
+        * Scripts/webkitpy/port/base.py:
+        (Port.jsc_results_directory): Returns the directory for the JSC test results JSON file.
+        * Scripts/webkitpy/tool/bot/earlywarningsystemtask.py:
+        (EarlyWarningSystemTask.run): Add check_patch_relevance step.
+        * Scripts/webkitpy/tool/bot/jscews_unittest.py: Added.
+        (MockPatchAnalysisTask): Mocked-out version of PatchAnalysisTask that doesn't run commands.
+        (MockPatchAnalysisTask.__init__): Sets attributes.
+        (MockPatchAnalysisTask._test): Override to not run command.
+        (MockPatchAnalysisTask._build_and_test_without_patch): Override to not run command.
+        (MockPatchAnalysisTask.validate): Assume mocked patch is valid for purposes of testing retry logic.
+        (MockPatchAnalysisTask.test_run_count): Specific to the mocked version, to test retry logic.
+        (MockJSCEarlyWarningSystem): Mocked-out version of AbstarctEarlyWarningSystem so we can provide test results.
+        (MockJSCEarlyWarningSystem.__init__): Sets attributes, also sets group to jsc.
+        (MockJSCEarlyWarningSystem.test_results): Returns test results provided by us, instead of using a JSON reader.
+        (JSCEarlyWarningSystemTest): Class to test retry logic in below situations.
+        (JSCEarlyWarningSystemTest._create_task): Helper function to abstract out common code.
+        (JSCEarlyWarningSystemTest._results_indicate_all_passed): False if input is None or has failures, else True.
+        (JSCEarlyWarningSystemTest.test_success_case): Clean patch on clean tree.
+        (JSCEarlyWarningSystemTest.test_test_failure): Failed patch on clean tree.
+        (JSCEarlyWarningSystemTest.test_fix): Patch that fixes all tree redness.
+        (JSCEarlyWarningSystemTest.test_ineffective_patch): Patch that has same failures as tree.
+        (JSCEarlyWarningSystemTest.test_partially_effective_patch): Patch fixes some failures but adds no new failures.
+        (JSCEarlyWarningSystemTest.test_different_test_failures_in_patch_and_tree): Patch has some failures not in tree.
+        (JSCEarlyWarningSystemTest.test_first_results_could_not_be_read): Patch results not readable.
+        (JSCEarlyWarningSystemTest.test_second_results_could_not_be_read): Patch results not readable on second run.
+        (JSCEarlyWarningSystemTest.test_clean_results_could_not_be_read): Results from clean tree not readable.
+        (JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_pass): Patch has one flake and no failures.
+        (JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_pass_v2): Patch has one flake and no failures.
+        (JSCEarlyWarningSystemTest.test_flaky_results_on_clean_tree_failure): Patch has flakes and failed tests.
+        (JSCEarlyWarningSystemTest.test_flaky_results_on_red_tree_pass): Patch has same failures as tree, plus a flake.
+        * Scripts/webkitpy/tool/bot/jsctestresultsreader.py: Added.
+        (JSCTestResultsReader): Reads results file.
+        (JSCTestResultsReader.__init__): Sets attributes.
+        (JSCTestResultsReader._read_file_contents): Reads file.
+        (JSCTestResultsReader.results): Reads the results file and returns a JSCTestResults object.
+        * Scripts/webkitpy/tool/bot/patchanalysistask.py:
+        (PatchIsNotApplicable): Exception for when patch doesn't have relevant changes.
+        (PatchAnalysisTask._check_patch_relevance): Added.
+        (PatchAnalysisTask._build): Check for JSC.
+        (PatchAnalysisTask._build_without_patch): Check for JSC.
+        (PatchAnalysisTask._test): Check for JSC.
+        (PatchAnalysisTask._build_and_test_without_patch): Check for JSC.
+        (PatchAnalysisTask._retry_jsc_tests): Retry logic for JSC EWS.
+        (PatchAnalysisTask._retry_layout_tests): Retry logic for layout tests EWS.
+        (PatchAnalysisTask._test_patch): Add retry logic for JSC.
+        * Scripts/webkitpy/tool/commands/download.py:
+        (CheckPatchRelevance): Add check-patch-relevance command.
+        * Scripts/webkitpy/tool/commands/earlywarningsystem.py:
+        (AbstractEarlyWarningSystem._create_task): Abstract out to enable mocking.
+        (AbstractEarlyWarningSystem.begin_work_queue): Use JSCTestResultsReader not LayoutTestResultsReader in JSC EWS.
+        (AbstractEarlyWarningSystem.review_patch): Handle PatchIsApplicable.
+        (AbstractEarlyWarningSystem.test_results): _layout_test_results_reader -> _test_results_reader.
+        (AbstractEarlyWarningSystem.archive_last_test_results): _layout_test_results_reader -> _test_results_reader.
+        (AbstractEarlyWarningSystem.group): This attribute determines the type of EWS (eg. JSC).
+        (AbstractEarlyWarningSystem.load_ews_classes): Add _group, and make classes of type cls to enable mocking.
+        * Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py:
+        (TestEWS): Sample layout test EWS class used for unit testing.
+        (TestJSCEWS): Sample JSC EWS class used for unit testing.
+        (AbstractEarlyWarningSystemTest.test_failing_tests_message.TestEWS): Add _group.
+        (AbstractEarlyWarningSystemTest.test_failing_jsc_tests_message.TestEWS): Added test for jsc failures message.
+        (AbstractEarlyWarningSystemTest): Add _group variable.
+        (EarlyWarningSystemTest._default_expected_logs): Add check-patch-relevance step, inconclusive logs, and group.
+        (EarlyWarningSystemTest._test_ews): Add logs_are_conclusive option to pass through to default_expected_logs().
+        (EarlyWarningSystemTest.test_inconclusive_test_results): Test not removing patch from queue if not conclusive.
+        (MockAbstractEarlyWarningSystemForInconclusiveJSCResults): Added.
+        (MockAbstractEarlyWarningSystemForInconclusiveJSCResults._test_patch): Simulates running tests but not getting a conclusive result.
+        (MockEarlyWarningSystemTaskForInconclusiveJSCResults): Added.
+        (MockEarlyWarningSystemTaskForInconclusiveJSCResults._create_task): Use MockEarlyWarningSystemTask (not EarlyWarningSystemTask).
+        * Scripts/webkitpy/tool/steps/__init__.py: Add CheckPatchRelevance import.
+        * Scripts/webkitpy/tool/steps/build.py:
+        (Build.options): Check for JSC.
+        (Build.build): Check for JSC.
+        (Build.run): Check for JSC.
+        * Scripts/webkitpy/tool/steps/checkpatchrelevance.py: Added.
+        (CheckPatchRelevance): Added.
+        (CheckPatchRelevance._does_contain_change_in_paths): Abstract function to perform regex matching.
+        (CheckPatchRelevance.run): Check if changed files in patch belong in certain folders.
+        * Scripts/webkitpy/tool/steps/options.py:
+        (Options): Add --group command line option.
+        * Scripts/webkitpy/tool/steps/runtests.py:
+        (RunTests.options): Add group.
+        (RunTests.run): Check for JSC.
+        (RunTests._run_webkit_tests): Check for JSC.
+        (RunTests): Add _group attribute.
+        (RunTests._run_javascriptcore_tests): New.
+        * Scripts/webkitpy/tool/steps/steps_unittest.py: Unit tests.
+
 2017-02-17  Aakash Jain  <aakash_jain@apple.com>
 
         Fix tools that were broken by Efl removal
index 6f95529..a0934e2 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2013 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -27,6 +28,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # These must be in sync with webkit-patch's AbstractQueue.
+skip_status = "Skip"
 pass_status = "Pass"
 fail_status = "Fail"
 error_status = "Error"
index b31a38e..0067686 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2014 Apple Inc. All rights reserved.
+# Copyright (C) 2014, 2017 Apple Inc. All rights reserved.
 # Copyright (C) 2013 Google Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,7 @@ all_queue_names = [
     "gtk-wk2-ews",
     "ios-ews",
     "ios-sim-ews",
+    "jsc-ews",
     "mac-ews",
     "mac-wk2-ews",
     "mac-debug-ews",
index c80514f..38bac03 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2009 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -182,11 +183,15 @@ class StatusBubble(webapp.RequestHandler):
         return bubble
 
     def _should_show_bubble_for(self, attachment, queue):
-         # Any pending queue is shown.
+        # Any pending queue is shown.
         if attachment.position_in_queue(queue):
             return True
-        # EWS queues are also shown when complete.
-        return bool(queue.is_ews() and attachment.status_for_queue(queue))
+
+        if not queue.is_ews():
+            return False
+
+        status = attachment.status_for_queue(queue)
+        return bool(status and not status.did_skip())
 
     def _build_bubbles_for_attachment(self, attachment):
         show_submit_to_ews = True
index 572dce0..674da29 100644 (file)
@@ -78,6 +78,7 @@ class Queue(object):
         display_name = display_name.replace("Wk2", "WK2")
         display_name = display_name.replace("Ews", "EWS")
         display_name = display_name.replace("Ios", "iOS")
+        display_name = display_name.replace("Jsc", "JSC")
         return display_name
 
     _dash_regexp = re.compile("-")
index d9bb6f6..971e6dc 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2013 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -40,3 +41,6 @@ class QueueStatus(db.Model, QueuePropertyMixin):
     message = db.StringProperty(multiline=True)
     date = db.DateTimeProperty(auto_now_add=True)
     results_file = db.BlobProperty()
+
+    def did_skip(self):
+        return self.message == messages.skip_status
index c5d10fc..9774ec8 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2011 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -37,6 +38,7 @@ class MockSCM(object):
         self.added_paths = set()
         self._filesystem = filesystem or MockFileSystem()
         self._executive = executive or MockExecutive()
+        self._mockChangedFiles = ["MockFile1"]
 
     def add(self, destination_path):
         self.add_list([destination_path])
@@ -71,7 +73,7 @@ class MockSCM(object):
         return self._filesystem.join(self.checkout_root, *comps)
 
     def changed_files(self, git_commit=None):
-        return ["MockFile1"]
+        return self._mockChangedFiles
 
     def changed_files_for_revision(self, revision):
         return ["MockFile1"]
index 273cf81..9fdcce0 100644 (file)
         "port": "mac",
         "name": "mac-32bit-ews",
         "architecture": "i386"
+    },
+    "JSC EWS": {
+        "port": "mac",
+        "name": "jsc-ews",
+        "group": "jsc",
+        "runTests": true
     }
 }
index a0d50dc..f6794ff 100644 (file)
@@ -1,6 +1,6 @@
 # Copyright (C) 2009, Google Inc. All rights reserved.
 # Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
-# Copyright (C) 2015 Apple Inc. All rights reserved.
+# Copyright (C) 2015, 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -95,24 +95,29 @@ class DeprecatedPort(object):
     def prepare_changelog_command(self):
         return self.script_shell_command("prepare-ChangeLog")
 
-    def build_webkit_command(self, build_style=None):
-        command = self.script_shell_command("build-webkit")
+    def _append_build_style_flag(self, command, build_style):
         if build_style == "debug":
             command.append("--debug")
-        if build_style == "release":
+        elif build_style == "release":
             command.append("--release")
         return command
 
-    def run_javascriptcore_tests_command(self):
-        return self.script_shell_command("run-javascriptcore-tests")
+    def build_webkit_command(self, build_style=None):
+        command = self.script_shell_command("build-webkit")
+        return self._append_build_style_flag(command, build_style)
+
+    def build_jsc_command(self, build_style=None):
+        command = self.script_shell_command("build-jsc")
+        return self._append_build_style_flag(command, build_style)
+
+    def run_javascriptcore_tests_command(self, build_style=None):
+        command = self.script_shell_command("run-javascriptcore-tests")
+        command.append("--no-fail-fast")
+        return self._append_build_style_flag(command, build_style)
 
     def run_webkit_tests_command(self, build_style=None):
         command = self.script_shell_command("run-webkit-tests")
-        if build_style == "debug":
-            command.append("--debug")
-        if build_style == "release":
-            command.append("--release")
-        return command
+        return self._append_build_style_flag(command, build_style)
 
     def run_python_unittests_command(self):
         return self.script_shell_command("test-webkitpy")
index 61743de..ca1b18a 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2011 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -49,7 +50,7 @@ class MockPort(object):
     def run_perl_unittests_command(self):
         return ['mock-test-webkitperl']
 
-    def run_javascriptcore_tests_command(self):
+    def run_javascriptcore_tests_command(self, build_style=None):
         return ['mock-run-javacriptcore-tests']
 
     def run_webkit_tests_command(self, build_style=None):
index 08d5452..16cd7f9 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (c) 2009, Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -39,6 +40,10 @@ class DeprecatedPortTest(unittest.TestCase):
         self.assertEqual(MacPort().build_webkit_command(build_style="debug"), DeprecatedPort().script_shell_command("build-webkit") + ["--debug"])
         self.assertEqual(MacPort().build_webkit_command(build_style="release"), DeprecatedPort().script_shell_command("build-webkit") + ["--release"])
 
+        self.assertEqual(MacPort().build_jsc_command(), DeprecatedPort().script_shell_command("build-jsc"))
+        self.assertEqual(MacPort().build_jsc_command(build_style="release"), DeprecatedPort().script_shell_command("build-jsc") + ["--release"])
+        self.assertEqual(MacPort().build_jsc_command(build_style="debug"), DeprecatedPort().script_shell_command("build-jsc") + ["--debug"])
+
     def test_gtk_wk2_port(self):
         self.assertEqual(GtkWK2Port().flag(), "--port=gtk-wk2")
         self.assertEqual(GtkWK2Port().run_webkit_tests_command(), DeprecatedPort().script_shell_command("run-webkit-tests") + ["--gtk"])
diff --git a/Tools/Scripts/webkitpy/common/net/abstracttestresults.py b/Tools/Scripts/webkitpy/common/net/abstracttestresults.py
new file mode 100644 (file)
index 0000000..30a7845
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 logging
+
+_log = logging.getLogger(__name__)
+
+
+class AbstractTestResults(object):
+    def failing_tests(self):
+        raise NotImplementedError("subclasses must implement")
+
+    def did_exceed_test_failure_limit(self):
+        raise NotImplementedError("subclasses must implement")
diff --git a/Tools/Scripts/webkitpy/common/net/jsctestresults.py b/Tools/Scripts/webkitpy/common/net/jsctestresults.py
new file mode 100644 (file)
index 0000000..527c2e0
--- /dev/null
@@ -0,0 +1,77 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 json
+import logging
+
+from webkitpy.common.net.abstracttestresults import AbstractTestResults
+
+_log = logging.getLogger(__name__)
+
+
+class JSCTestResults(AbstractTestResults):
+    def __init__(self, all_api_tests_passed, stress_test_failures):
+        self._all_api_tests_passed = all_api_tests_passed
+        self._stress_test_failures = stress_test_failures
+
+        self._failing_test_names = stress_test_failures[:]
+        if not self._all_api_tests_passed:
+            self._failing_test_names.append('apiTests')
+
+    @classmethod
+    def intersection(cls, first, second):
+        intersection_api_tests_passed = first._all_api_tests_passed or second._all_api_tests_passed
+        intersection_stress_test_failures = list(set(first._stress_test_failures) & set(second._stress_test_failures))
+        return cls(intersection_api_tests_passed, intersection_stress_test_failures)
+
+    @classmethod
+    def results_from_string(cls, string):
+        if not string:
+            return None
+
+        try:
+            parsed_results = json.loads(string)
+        except ValueError:
+            _log.error('Invalid JSON results')
+            return None
+
+        if 'allApiTestsPassed' not in parsed_results or 'stressTestFailures' not in parsed_results:
+            return None
+
+        return cls(parsed_results['allApiTestsPassed'], parsed_results['stressTestFailures'])
+
+    def equals(self, other):
+        return (self._all_api_tests_passed == other._all_api_tests_passed and
+               set(self._stress_test_failures) == set(other._stress_test_failures))
+
+    def is_subset(self, other):
+        return set(self._failing_test_names) <= set(other._failing_test_names)
+
+    def all_passed(self):
+        return self._all_api_tests_passed and not self._stress_test_failures
+
+    def failing_tests(self):
+        return self._failing_test_names
+
+    # No defined failure limit for JSC.
+    def did_exceed_test_failure_limit(self):
+        return False
diff --git a/Tools/Scripts/webkitpy/common/net/jsctestresults_unittest.py b/Tools/Scripts/webkitpy/common/net/jsctestresults_unittest.py
new file mode 100644 (file)
index 0000000..4cf1149
--- /dev/null
@@ -0,0 +1,75 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 unittest
+
+from webkitpy.common.net.jsctestresults import JSCTestResults
+
+
+class JSCTestResultsTest(unittest.TestCase):
+    def test_results_from_string(self):
+        none_item = None
+        empty_json = ''
+        invalid_json = '{"allApiTestsPassed":'
+        incomplete_json_v1 = '{"allApiTestsPassed": true}'
+        incomplete_json_v2 = '{"stressTestFailures":[]}'
+        self.assertEqual(None, JSCTestResults.results_from_string(none_item))
+        self.assertEqual(None, JSCTestResults.results_from_string(empty_json))
+        self.assertEqual(None, JSCTestResults.results_from_string(invalid_json))
+        self.assertEqual(None, JSCTestResults.results_from_string(incomplete_json_v1))
+        self.assertEqual(None, JSCTestResults.results_from_string(incomplete_json_v2))
+
+        no_failures_string = '{"allApiTestsPassed": true, "stressTestFailures":[]}'
+        no_failures_results = JSCTestResults(True, [])
+        self.assertTrue(no_failures_results.equals(JSCTestResults.results_from_string(no_failures_string)))
+
+        api_test_failures_string = '{"allApiTestsPassed": false, "stressTestFailures":[]}'
+        api_test_failures_results = JSCTestResults(False, [])
+        self.assertTrue(api_test_failures_results.equals(JSCTestResults.results_from_string(api_test_failures_string)))
+
+        many_failures_string = '{"allApiTestsPassed": false, "stressTestFailures":["es6.yaml/es6/typed_arrays_Int16Array.js.default", "es6.yaml/es6/typed_arrays_Int8Array.js.default"]}'
+        many_failures_results = JSCTestResults(False, ["es6.yaml/es6/typed_arrays_Int16Array.js.default", "es6.yaml/es6/typed_arrays_Int8Array.js.default"])
+        self.assertTrue(many_failures_results.equals(JSCTestResults.results_from_string(many_failures_string)))
+
+        self.assertFalse(no_failures_results == api_test_failures_results)
+        self.assertFalse(api_test_failures_results == many_failures_results)
+
+    def test_intersection_api_tests(self):
+        results1 = JSCTestResults(False, [])
+        results2 = JSCTestResults(True, [])
+
+        expected_intersection = JSCTestResults(True, [])
+        self.assertTrue(expected_intersection.equals(JSCTestResults.intersection(results1, results2)))
+
+    def test_intersection_stress_tests(self):
+        results1 = JSCTestResults(True, ['failure1', 'failure2'])
+        results2 = JSCTestResults(True, ['failure1', 'failure3'])
+
+        expected_intersection = JSCTestResults(True, ['failure1'])
+        self.assertTrue(expected_intersection.equals(JSCTestResults.intersection(results1, results2)))
+
+    def test_intersection_general_case(self):
+        results1 = JSCTestResults(True, ['failure1', 'failure2'])
+        results2 = JSCTestResults(False, ['failure1'])
+
+        expected_intersection = JSCTestResults(True, ['failure1'])
+        self.assertTrue(expected_intersection.equals(JSCTestResults.intersection(results1, results2)))
index 7fb67c1..70616ed 100644 (file)
@@ -28,6 +28,7 @@
 
 import logging
 
+from webkitpy.common.net.abstracttestresults import AbstractTestResults
 from webkitpy.common.net.resultsjsonparser import ParsedJSONResults
 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, SoupStrainer
 from webkitpy.layout_tests.models import test_results
@@ -45,7 +46,7 @@ def path_for_layout_test(test_name):
 # FIXME: This should be unified with ResultsSummary or other NRWT layout tests code
 # in the layout_tests package.
 # This doesn't belong in common.net, but we don't have a better place for it yet.
-class LayoutTestResults(object):
+class LayoutTestResults(AbstractTestResults):
     @classmethod
     def results_from_string(cls, string):
         if not string:
index 01e82c2..6a6142a 100644 (file)
@@ -808,6 +808,9 @@ class Port(object):
         inverse of relative_test_filename()."""
         return self._filesystem.join(self.layout_tests_dir(), test_name)
 
+    def jsc_results_directory(self):
+        return self._build_path()
+
     def results_directory(self):
         """Absolute path to the place to store the test results (uses --results-directory)."""
         if not self._results_directory:
index fc308b4..ecfacd0 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (c) 2011 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -26,7 +27,7 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from webkitpy.tool.bot.patchanalysistask import PatchAnalysisTask, PatchAnalysisTaskDelegate, UnableToApplyPatch, PatchIsNotValid
+from webkitpy.tool.bot.patchanalysistask import PatchAnalysisTask, PatchAnalysisTaskDelegate, UnableToApplyPatch, PatchIsNotValid, PatchIsNotApplicable
 
 
 class EarlyWarningSystemTaskDelegate(PatchAnalysisTaskDelegate):
@@ -58,6 +59,8 @@ class EarlyWarningSystemTask(PatchAnalysisTask):
             return False
         if not self._apply():
             raise UnableToApplyPatch(self._patch)
+        if not self._check_patch_relevance():
+            raise PatchIsNotApplicable(self._patch)
         if not self._build():
             if not self._build_without_patch():
                 return False
diff --git a/Tools/Scripts/webkitpy/tool/bot/jscews_unittest.py b/Tools/Scripts/webkitpy/tool/bot/jscews_unittest.py
new file mode 100644 (file)
index 0000000..a6aef3d
--- /dev/null
@@ -0,0 +1,218 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 logging
+import unittest
+
+from webkitpy.common.net.jsctestresults import JSCTestResults
+from webkitpy.common.system.executive import ScriptError
+from webkitpy.tool.bot.patchanalysistask import *
+from webkitpy.tool.commands.earlywarningsystem import AbstractEarlyWarningSystem
+from webkitpy.tool.mocktool import MockTool
+
+_log = logging.getLogger(__name__)
+
+
+class MockPatchAnalysisTask(PatchAnalysisTask):
+    def __init__(self, delegate, patch, patches_passed_all_tests):
+        self._delegate = delegate
+        self._patch = patch
+        self._script_error = None
+        self._results_archive_from_patch_test_run = None
+        self._results_from_patch_test_run = None
+        self.error = None
+        self._patches_passed_all_tests = patches_passed_all_tests
+        self._test_run_count = 0
+        self.failure_status_id = 0
+
+    def _test(self):
+        self._test_run_count += 1
+
+        if self._patches_passed_all_tests.pop() == True:
+            return True
+        self._script_error = ScriptError('Regression test')
+        return False
+
+    def _build_and_test_without_patch(self):
+        self._test_run_count += 1
+        return True
+
+    def validate(self):
+        return True
+
+    def test_run_count(self):
+        return self._test_run_count
+
+
+# This is the delegate to be used with MockPatchAnalysisTask, above.
+class MockJSCEarlyWarningSystem(AbstractEarlyWarningSystem):
+    def __init__(self, first_test_results, second_test_results, clean_test_results):
+        AbstractEarlyWarningSystem.__init__(self)
+        self._group = 'jsc'
+        self._results_in_order = [clean_test_results, second_test_results, first_test_results]
+
+    def test_results(self):
+        return self._results_in_order.pop()
+
+
+class JSCEarlyWarningSystemTest(unittest.TestCase):
+    def _results_indicate_all_passed(self, results):
+        if results == None:
+            return False
+        return results.all_passed()
+
+    def _create_task(self, first_test_results, second_test_results, clean_test_results):
+        queue = MockJSCEarlyWarningSystem(first_test_results, second_test_results, clean_test_results)
+        tool = MockTool(log_executive=True)
+        patch = tool.bugs.fetch_attachment(10000)
+        patches_passed_all_tests = map(self._results_indicate_all_passed, [second_test_results, first_test_results])
+        return MockPatchAnalysisTask(queue, patch, patches_passed_all_tests)
+
+    def test_success_case(self):
+        first_test_results = JSCTestResults(True, [])
+        second_test_results = JSCTestResults(True, [])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 1)
+        self.assertTrue(return_value)
+
+    def test_test_failure(self):
+        first_test_results = JSCTestResults(True, ['Fail.js'])
+        second_test_results = JSCTestResults(True, ['Fail.js'])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        with self.assertRaises(ScriptError):
+            return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+
+    def test_fix(self):
+        first_test_results = JSCTestResults(True, [])
+        second_test_results = JSCTestResults(True, [])
+        clean_test_results = JSCTestResults(True, ['Fail.js'])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 1)
+        self.assertTrue(return_value)
+
+    def test_ineffective_patch(self):
+        first_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        second_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        clean_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+        self.assertTrue(return_value)
+
+    def test_partially_effective_patch(self):
+        first_test_results = JSCTestResults(True, ['failure2.js'])
+        second_test_results = JSCTestResults(True, ['failure2.js'])
+        clean_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+        self.assertTrue(return_value)
+
+    def test_different_test_failures_in_patch_and_tree(self):
+        first_test_results = JSCTestResults(False, [])
+        second_test_results = JSCTestResults(False, [])
+        clean_test_results = JSCTestResults(True, ['failure1.js', 'failure2.js'])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        with self.assertRaises(ScriptError):
+            return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+
+    def test_first_results_could_not_be_read(self):
+        first_test_results = None
+        second_test_results = JSCTestResults(True, [])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 1)
+        self.assertFalse(return_value)
+
+    def test_second_results_could_not_be_read(self):
+        first_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        second_test_results = None
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 2)
+        self.assertFalse(return_value)
+
+    def test_clean_results_could_not_be_read(self):
+        first_test_results = JSCTestResults(True, ['failure2.js'])
+        second_test_results = JSCTestResults(True, ['failure2.js'])
+        clean_test_results = None
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+        self.assertFalse(return_value)
+
+    def test_flaky_results_on_clean_tree_pass(self):
+        first_test_results = JSCTestResults(True, ['failure2.js'])
+        second_test_results = JSCTestResults(True, [])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertTrue(return_value)
+        self.assertEqual(task.test_run_count(), 2)
+
+    def test_flaky_results_on_clean_tree_pass_v2(self):
+        first_test_results = JSCTestResults(True, [])
+        second_test_results = JSCTestResults(True, ['failure2.js'])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertTrue(return_value)
+        self.assertEqual(task.test_run_count(), 1)
+
+    def test_flaky_results_on_clean_tree_failure(self):
+        first_test_results = JSCTestResults(False, ['failure1.js', 'failure2.js'])
+        second_test_results = JSCTestResults(True, ['failure2.js'])
+        clean_test_results = JSCTestResults(True, [])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        with self.assertRaises(ScriptError):
+            task._test_patch()
+        self.assertEqual(task.test_run_count(), 3)
+
+    def test_flaky_results_on_red_tree_pass(self):
+        first_test_results = JSCTestResults(True, ['failure1.js'])
+        second_test_results = JSCTestResults(True, ['failure1.js', 'failure2.js'])
+        clean_test_results = JSCTestResults(True, ['failure1.js'])
+        task = self._create_task(first_test_results, second_test_results, clean_test_results)
+
+        return_value = task._test_patch()
+        self.assertTrue(return_value)
+        self.assertEqual(task.test_run_count(), 3)
diff --git a/Tools/Scripts/webkitpy/tool/bot/jsctestresultsreader.py b/Tools/Scripts/webkitpy/tool/bot/jsctestresultsreader.py
new file mode 100644 (file)
index 0000000..b3265ab
--- /dev/null
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 logging
+
+from webkitpy.common.net.jsctestresults import JSCTestResults
+
+_log = logging.getLogger(__name__)
+
+
+class JSCTestResultsReader(object):
+    def __init__(self, host, results_directory):
+        self._host = host
+        self._results_directory = results_directory
+
+    def _read_file_contents(self, path):
+        try:
+            return self._host.filesystem.read_text_file(path)
+        except (IOError, KeyError):
+            return None
+
+    def results(self):
+        results_path = self._host.filesystem.join(self._results_directory, 'jsc_test_results.json')
+        contents = self._read_file_contents(results_path)
+        return JSCTestResults.results_from_string(contents)
index 7523164..aca82fd 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (c) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -28,6 +29,7 @@
 
 from webkitpy.common.system.executive import ScriptError
 from webkitpy.common.net.layouttestresults import LayoutTestResults
+from webkitpy.common.net.jsctestresults import JSCTestResults
 
 
 class UnableToApplyPatch(Exception):
@@ -43,6 +45,11 @@ class PatchIsNotValid(Exception):
         self.failure_message = failure_message
 
 
+class PatchIsNotApplicable(Exception):
+    def __init__(self, patch):
+        Exception.__init__(self)
+        self.patch = patch
+
 class PatchAnalysisTaskDelegate(object):
     def parent_command(self):
         raise NotImplementedError("subclasses must implement")
@@ -122,28 +129,44 @@ class PatchAnalysisTask(object):
         "Applied patch",
         "Patch does not apply")
 
+    def _check_patch_relevance(self):
+        args = [
+            "check-patch-relevance",
+        ]
+
+        if hasattr(self._delegate, 'group'):
+            args.append("--group=%s" % self._delegate.group())
+
+        return self._run_command(args, "Checked relevance of patch", "Patch was not relevant")
+
     def _build(self):
-        return self._run_command([
+        args = [
             "build",
             "--no-clean",
             "--no-update",
             "--build-style=%s" % self._delegate.build_style(),
-        ],
-        "Built patch",
-        "Patch does not build")
+        ]
+
+        if hasattr(self._delegate, 'group'):
+            args.append("--group=%s" % self._delegate.group())
+
+        return self._run_command(args, "Built patch", "Patch does not build")
 
     def _build_without_patch(self):
-        return self._run_command([
+        args = [
             "build",
             "--force-clean",
             "--no-update",
             "--build-style=%s" % self._delegate.build_style(),
-        ],
-        "Able to build without patch",
-        "Unable to build without patch")
+        ]
+
+        if hasattr(self._delegate, 'group'):
+            args.append("--group=%s" % self._delegate.group())
+
+        return self._run_command(args, "Able to build without patch", "Unable to build without patch")
 
     def _test(self):
-        return self._run_command([
+        args = [
             "build-and-test",
             "--no-clean",
             "--no-update",
@@ -151,12 +174,15 @@ class PatchAnalysisTask(object):
             "--test",
             "--non-interactive",
             "--build-style=%s" % self._delegate.build_style(),
-        ],
-        "Passed tests",
-        "Patch does not pass tests")
+        ]
+
+        if hasattr(self._delegate, 'group'):
+            args.append("--group=%s" % self._delegate.group())
+
+        return self._run_command(args, "Passed tests", "Patch does not pass tests")
 
     def _build_and_test_without_patch(self):
-        return self._run_command([
+        args = [
             "build-and-test",
             "--force-clean",
             "--no-update",
@@ -164,9 +190,12 @@ class PatchAnalysisTask(object):
             "--test",
             "--non-interactive",
             "--build-style=%s" % self._delegate.build_style(),
-        ],
-        "Able to pass tests without patch",
-        "Unable to pass tests without patch (tree is red?)")
+        ]
+
+        if hasattr(self._delegate, 'group'):
+            args.append("--group=%s" % self._delegate.group())
+
+        return self._run_command(args, "Able to pass tests without patch", "Unable to pass tests without patch (tree is red?)")
 
     def _land(self):
         # Unclear if this should pass --quiet or not.  If --parent-command always does the reporting, then it should.
@@ -206,10 +235,35 @@ class PatchAnalysisTask(object):
         # also present without the patch, so we don't need to defer.
         return False
 
-    def _test_patch(self):
+    # FIXME: Abstract out common parts of the retry logic.
+    def _retry_jsc_tests(self):
+        first_results = self._delegate.test_results()
+        first_script_error = self._script_error
+        first_failure_status_id = self.failure_status_id
+        if first_results is None:
+            return False
+
         if self._test():
             return True
+        second_results = self._delegate.test_results()
+        second_script_error = self._script_error
+        if second_results is None:
+            return False
+
+        consistently_failing_test_results = JSCTestResults.intersection(first_results, second_results)
+
+        self._build_and_test_without_patch()
+        clean_tree_results = self._delegate.test_results()
+        if clean_tree_results is None:
+            return False
+
+        if consistently_failing_test_results.is_subset(clean_tree_results):
+            return True
+
+        self.failure_status_id = first_failure_status_id
+        return self.report_failure(None, consistently_failing_test_results, first_script_error)
 
+    def _retry_layout_tests(self):
         # Note: archive_last_test_results deletes the results directory, making these calls order-sensitve.
         # We could remove this dependency by building the test_results from the archive.
         first_results = self._delegate.test_results()
@@ -240,11 +294,13 @@ class PatchAnalysisTask(object):
             return self.report_failure(first_results_archive, first_results, first_script_error)
 
         if second_results.did_exceed_test_failure_limit():
-            self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id)
+            self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive,
+                                              first_script_error, first_failure_status_id)
             return False
 
         if first_results.did_exceed_test_failure_limit():
-            self._should_defer_patch_or_throw(second_results.failing_test_results(), second_results_archive, second_script_error, second_failure_status_id)
+            self._should_defer_patch_or_throw(second_results.failing_test_results(), second_results_archive,
+                                              second_script_error, second_failure_status_id)
             return False
 
         if self._results_failed_different_tests(first_results, second_results):
@@ -259,14 +315,16 @@ class PatchAnalysisTask(object):
 
             tests_that_consistently_failed = first_failing_results_set.intersection(second_failing_results_set)
             if tests_that_consistently_failed:
-                if self._should_defer_patch_or_throw(tests_that_consistently_failed, first_results_archive, first_script_error, first_failure_status_id):
+                if self._should_defer_patch_or_throw(tests_that_consistently_failed, first_results_archive,
+                                                     first_script_error, first_failure_status_id):
                     return False  # Defer patch
 
             # At this point we know that at least one test flaked, but no consistent failures
             # were introduced. This is a bit of a grey-zone.
             return False  # Defer patch
 
-        if self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive, first_script_error, first_failure_status_id):
+        if self._should_defer_patch_or_throw(first_results.failing_test_results(), first_results_archive,
+                                             first_script_error, first_failure_status_id):
             return False  # Defer patch
 
         # At this point, we know that the first and second runs had the exact same failures,
@@ -274,6 +332,15 @@ class PatchAnalysisTask(object):
         # that the patch is good.
         return True
 
+    def _test_patch(self):
+        if self._test():
+            return True
+
+        if hasattr(self._delegate, 'group') and self._delegate.group() == "jsc":
+            return self._retry_jsc_tests()
+        else:
+            return self._retry_layout_tests()
+
     def results_archive_from_patch_test_run(self, patch):
         assert(self._patch.id() == patch.id())  # PatchAnalysisTask is not currently re-useable.
         return self._results_archive_from_patch_test_run
index e5169aa..14b55bc 100644 (file)
@@ -1,5 +1,5 @@
 # Copyright (c) 2009, 2011 Google Inc. All rights reserved.
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009, 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -87,6 +87,14 @@ class BuildAndTest(AbstractSequencedCommand):
     ]
 
 
+class CheckPatchRelevance(AbstractSequencedCommand):
+    name = "check-patch-relevance"
+    help_text = "Check if this patch needs to be tested"
+    steps = [
+        steps.CheckPatchRelevance,
+    ]
+
+
 class Land(AbstractSequencedCommand):
     name = "land"
     help_text = "Land the current working directory diff and updates the associated bug if any"
index 429da12..59c9488 100644 (file)
@@ -89,6 +89,7 @@ class DownloadCommandsTest(CommandsTest):
         options.update = True
         options.architecture = 'MOCK ARCH'
         options.iterate_on_new_tests = 0
+        options.group = None
         return options
 
     def test_build(self):
index 9bc0418..68f1d80 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (c) 2009 Google Inc. All rights reserved.
+# Copyright (c) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -28,6 +29,7 @@
 
 import json
 import logging
+import os
 from optparse import make_option
 
 from webkitpy.common.config.committers import CommitterList
@@ -36,7 +38,8 @@ from webkitpy.common.system.filesystem import FileSystem
 from webkitpy.common.system.executive import ScriptError
 from webkitpy.tool.bot.earlywarningsystemtask import EarlyWarningSystemTask, EarlyWarningSystemTaskDelegate
 from webkitpy.tool.bot.layouttestresultsreader import LayoutTestResultsReader
-from webkitpy.tool.bot.patchanalysistask import UnableToApplyPatch, PatchIsNotValid
+from webkitpy.tool.bot.jsctestresultsreader import JSCTestResultsReader
+from webkitpy.tool.bot.patchanalysistask import UnableToApplyPatch, PatchIsNotValid, PatchIsNotApplicable
 from webkitpy.tool.bot.queueengine import QueueEngine
 from webkitpy.tool.commands.queues import AbstractReviewQueue
 
@@ -53,7 +56,11 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
 
     def begin_work_queue(self):
         AbstractReviewQueue.begin_work_queue(self)
-        self._layout_test_results_reader = LayoutTestResultsReader(self._tool, self._port.results_directory(), self._log_directory())
+
+        if self.group() == "jsc":
+            self._test_results_reader = JSCTestResultsReader(self._tool, self._port.jsc_results_directory())
+        else:
+            self._test_results_reader = LayoutTestResultsReader(self._tool, self._port.results_directory(), self._log_directory())
 
     def _failing_tests_message(self, task, patch):
         results = task.results_from_patch_test_run(patch)
@@ -79,8 +86,12 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
             tool.bugs.add_cc_to_bug(patch.bug_id(), self.watchers)
         tool.bugs.set_flag_on_attachment(patch.id(), "commit-queue", "-", message)
 
+    # This exists for mocking
+    def _create_task(self, patch):
+        return EarlyWarningSystemTask(self, patch, self._options.run_tests)
+
     def review_patch(self, patch):
-        task = EarlyWarningSystemTask(self, patch, self._options.run_tests)
+        task = self._create_task(patch)
         try:
             succeeded = task.run()
             if not succeeded:
@@ -93,6 +104,9 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
         except UnableToApplyPatch, e:
             self._did_error(patch, "%s unable to apply patch." % self.name)
             return False
+        except PatchIsNotApplicable, e:
+            self._did_skip(patch)
+            return False
         except ScriptError, e:
             self._post_reject_message_on_bug(self._tool, patch, task.failure_status_id, self._failing_tests_message(task, patch))
             results_archive = task.results_archive_from_patch_test_run(patch)
@@ -117,14 +131,17 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
         return self._update_status(message, patch=patch, results_file=failure_log)
 
     def test_results(self):
-        return self._layout_test_results_reader.results()
+        return self._test_results_reader.results()
 
     def archive_last_test_results(self, patch):
-        return self._layout_test_results_reader.archive(patch)
+        return self._test_results_reader.archive(patch)
 
     def build_style(self):
         return self._build_style
 
+    def group(self):
+        return self._group
+
     def refetch_patch(self, patch):
         return self._tool.bugs.fetch_attachment(patch.id())
 
@@ -149,12 +166,13 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
 
         classes = []
         for name, config in ewses.iteritems():
-            classes.append(type(name.encode('utf-8').translate(None, ' -'), (AbstractEarlyWarningSystem,), {
+            classes.append(type(name.encode('utf-8').translate(None, ' -'), (cls,), {
                 'name': config.get('name', config['port'] + '-ews'),
                 'port_name': config['port'],
                 'architecture': config.get('architecture', None),
                 '_build_style': config.get('style', "release"),
                 'watchers': config.get('watchers', []),
                 'run_tests': config.get('runTests', cls.run_tests),
+                '_group': config.get('group', None),
             }))
         return classes
index 0888781..f8beddc 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2009 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -29,6 +30,7 @@
 from webkitpy.thirdparty.mock import Mock
 from webkitpy.common.host import Host
 from webkitpy.common.host_mock import MockHost
+from webkitpy.common.net.jsctestresults import JSCTestResults
 from webkitpy.common.net.layouttestresults import LayoutTestResults
 from webkitpy.common.system.outputcapture import OutputCapture
 from webkitpy.layout_tests.models import test_results
@@ -41,62 +43,100 @@ from webkitpy.tool.commands.queuestest import QueuesTest
 from webkitpy.tool.mocktool import MockTool, MockOptions
 
 
-class AbstractEarlyWarningSystemTest(QueuesTest):
-    def test_failing_tests_message(self):
-        # Needed to define port_name, used in AbstractEarlyWarningSystem.__init__
-        class TestEWS(AbstractEarlyWarningSystem):
-            port_name = "win"  # Needs to be a port which port/factory understands.
-            _build_style = None
+# Needed to define port_name, used in AbstractEarlyWarningSystem.__init__
+class TestEWS(AbstractEarlyWarningSystem):
+    port_name = "win"  # Needs to be a port which port/factory understands.
+    _build_style = None
+    _group = None
 
-        ews = TestEWS()
+
+class TestJSCEWS(AbstractEarlyWarningSystem):
+    port_name = "mac"  # Needs to be a port which port/factory understands.
+    _build_style = None
+    _group = "jsc"
+
+
+class AbstractEarlyWarningSystemTest(QueuesTest):
+    def _test_message(self, ews, results, message):
         ews.bind_to_tool(MockTool())
         ews.host = MockHost()
         ews._options = MockOptions(port=None, confirm=False)
         OutputCapture().assert_outputs(self, ews.begin_work_queue, expected_logs=self._default_begin_work_queue_logs(ews.name))
         task = Mock()
-        task.results_from_patch_test_run = lambda a: LayoutTestResults([test_results.TestResult("foo.html", failures=[test_failures.FailureTextMismatch()]),
-                                                                          test_results.TestResult("bar.html", failures=[test_failures.FailureTextMismatch()])],
-                                                                          did_exceed_test_failure_limit=False)
+        task.results_from_patch_test_run = results
         patch = ews._tool.bugs.fetch_attachment(10000)
-        self.assertMultiLineEqual(ews._failing_tests_message(task, patch), "New failing tests:\nfoo.html\nbar.html")
+        self.assertMultiLineEqual(ews._failing_tests_message(task, patch), message)
+
+    def test_failing_tests_message(self):
+        ews = TestEWS()
+        results = lambda a: LayoutTestResults([test_results.TestResult("foo.html", failures=[test_failures.FailureTextMismatch()]),
+                                                test_results.TestResult("bar.html", failures=[test_failures.FailureTextMismatch()])],
+                                                did_exceed_test_failure_limit=False)
+        message = "New failing tests:\nfoo.html\nbar.html"
+        self._test_message(ews, results, message)
+
+    def test_failing_jsc_tests_message(self):
+        ews = TestJSCEWS()
+        results = lambda a: JSCTestResults(False, ["es6.yaml/es6/typed_arrays_Int8Array.js.default", "es6.yaml/es6/typed_arrays_Uint8Array.js.default"])
+        message = "New failing tests:\nes6.yaml/es6/typed_arrays_Int8Array.js.default\nes6.yaml/es6/typed_arrays_Uint8Array.js.default\napiTests"
+        self._test_message(ews, results, message)
+
+
+class MockEarlyWarningSystemTaskForInconclusiveJSCResults(EarlyWarningSystemTask):
+    def _test_patch(self):
+        self._test()
+        results = self._delegate.test_results()
+        return bool(results)
+
+
+class MockAbstractEarlyWarningSystemForInconclusiveJSCResults(AbstractEarlyWarningSystem):
+    def _create_task(self, patch):
+        task = MockEarlyWarningSystemTaskForInconclusiveJSCResults(self, patch, self._options.run_tests)
+        return task
 
 
 class EarlyWarningSystemTest(QueuesTest):
-    def _default_expected_logs(self, ews):
+    def _default_expected_logs(self, ews, conclusive):
         string_replacements = {
             "name": ews.name,
             "port": ews.port_name,
             "architecture": " --architecture=%s" % ews.architecture if ews.architecture else "",
             "build_style": ews.build_style(),
+            "group": ews.group(),
         }
         if ews.run_tests:
-            run_tests_line = "Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --build-style=%(build_style)s --port=%(port)s%(architecture)s\n" % string_replacements
+            run_tests_line = "Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --build-style=%(build_style)s --group=%(group)s --port=%(port)s%(architecture)s\n" % string_replacements
         else:
             run_tests_line = ""
         string_replacements['run_tests_line'] = run_tests_line
 
+        if conclusive:
+            result_lines = "MOCK: update_status: %(name)s Pass\nMOCK: release_work_item: %(name)s 10000\n" % string_replacements
+        else:
+            result_lines = "MOCK: release_lock: %(name)s 10000\n" % string_replacements
+        string_replacements['result_lines'] = result_lines
+
         expected_logs = {
             "begin_work_queue": self._default_begin_work_queue_logs(ews.name),
             "process_work_item": """MOCK: update_status: %(name)s Started processing patch
 Running: webkit-patch --status-host=example.com clean --port=%(port)s%(architecture)s
 Running: webkit-patch --status-host=example.com update --port=%(port)s%(architecture)s
 Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000 --port=%(port)s%(architecture)s
-Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=%(build_style)s --port=%(port)s%(architecture)s
-%(run_tests_line)sMOCK: update_status: %(name)s Pass
-MOCK: release_work_item: %(name)s 10000
-""" % string_replacements,
+Running: webkit-patch --status-host=example.com check-patch-relevance --group=%(group)s --port=%(port)s%(architecture)s
+Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=%(build_style)s --group=%(group)s --port=%(port)s%(architecture)s
+%(run_tests_line)s%(result_lines)s""" % string_replacements,
             "handle_unexpected_error": "Mock error message\n",
             "handle_script_error": "ScriptError error message\n\nMOCK output\n",
         }
         return expected_logs
 
-    def _test_ews(self, ews):
+    def _test_ews(self, ews, results_are_conclusive=True):
         ews.bind_to_tool(MockTool())
         ews.host = MockHost()
         options = Mock()
         options.port = None
         options.run_tests = ews.run_tests
-        self.assert_queue_outputs(ews, expected_logs=self._default_expected_logs(ews), options=options)
+        self.assert_queue_outputs(ews, expected_logs=self._default_expected_logs(ews, results_are_conclusive), options=options)
 
     def test_ewses(self):
         classes = AbstractEarlyWarningSystem.load_ews_classes()
@@ -104,3 +144,11 @@ MOCK: release_work_item: %(name)s 10000
         self.maxDiff = None
         for ews_class in classes:
             self._test_ews(ews_class())
+
+    def test_inconclusive_jsc_test_results(self):
+        classes = MockAbstractEarlyWarningSystemForInconclusiveJSCResults.load_ews_classes()
+        self.assertTrue(classes)
+        self.maxDiff = None
+        test_classes = filter(lambda x: x.run_tests and x.group == "jsc", classes)
+        for ews_class in test_classes:
+            self._test_ews(ews_class(), False)
index 1c71aac..fe2127c 100644 (file)
@@ -1,5 +1,5 @@
 # Copyright (c) 2009 Google Inc. All rights reserved.
-# Copyright (c) 2009 Apple Inc. All rights reserved.
+# Copyright (c) 2009, 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -62,6 +62,7 @@ class AbstractQueue(Command, QueueEngineDelegate):
     watchers = [
     ]
 
+    _skip_status = "Skip"
     _pass_status = "Pass"
     _fail_status = "Fail"
     _error_status = "Error"
@@ -244,6 +245,10 @@ class AbstractPatchQueue(AbstractQueue):
         self._update_status(message, patch)
         self._release_work_item(patch)
 
+    def _did_skip(self, patch):
+        self._update_status(self._skip_status, patch)
+        self._release_work_item(patch)
+
     def _unlock_patch(self, patch):
         self._tool.status_server.release_lock(self.name, patch)
 
index 356acab..6c9c09a 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -33,6 +34,7 @@ from webkitpy.tool.steps.applypatchwithlocalcommit import ApplyPatchWithLocalCom
 from webkitpy.tool.steps.applywatchlist import ApplyWatchList
 from webkitpy.tool.steps.attachtobug import AttachToBug
 from webkitpy.tool.steps.build import Build
+from webkitpy.tool.steps.checkpatchrelevance import CheckPatchRelevance
 from webkitpy.tool.steps.checkstyle import CheckStyle
 from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory
 from webkitpy.tool.steps.closebug import CloseBug
index f74ffce..6d648e3 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -41,28 +42,35 @@ class Build(AbstractStep):
             Options.build,
             Options.quiet,
             Options.build_style,
+            Options.group,
         ]
 
-    def build(self, build_style):
+    def build(self, build_style, group):
         environment = self._tool.copy_current_environment()
         environment.disable_gcc_smartquotes()
         environment.disable_jhbuild_VT100_output()
         env = environment.to_dictionary()
 
-        build_webkit_command = self._tool.deprecated_port().build_webkit_command(build_style=build_style)
+        if group == "jsc":
+            build_command = self._tool.deprecated_port().build_jsc_command(build_style=build_style)
+        else:
+            build_command = self._tool.deprecated_port().build_webkit_command(build_style=build_style)
 
         if self._options.architecture:
-            build_webkit_command += ['ARCHS=%s' % self._options.architecture]
+            build_command += ['ARCHS=%s' % self._options.architecture]
 
-        self._tool.executive.run_and_throw_if_fail(build_webkit_command, self._options.quiet,
+        self._tool.executive.run_and_throw_if_fail(build_command, self._options.quiet,
             cwd=self._tool.scm().checkout_root, env=env)
 
     def run(self, state):
         if not self._options.build:
             return
         _log.info("Building WebKit")
+
+        group = self._options.group
+
         if self._options.build_style == "both":
-            self.build("debug")
-            self.build("release")
+            self.build("debug", group)
+            self.build("release", group)
         else:
-            self.build(self._options.build_style)
+            self.build(self._options.build_style, group)
diff --git a/Tools/Scripts/webkitpy/tool/steps/checkpatchrelevance.py b/Tools/Scripts/webkitpy/tool/steps/checkpatchrelevance.py
new file mode 100644 (file)
index 0000000..103e163
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (C) 2017 Apple Inc. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS 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 logging
+import re
+
+from webkitpy.common.system.executive import ScriptError
+from webkitpy.tool.steps.abstractstep import AbstractStep
+from webkitpy.tool.steps.options import Options
+
+_log = logging.getLogger(__name__)
+
+
+class CheckPatchRelevance(AbstractStep):
+    @classmethod
+    def options(cls):
+        return AbstractStep.options() + [
+            Options.group,
+        ]
+
+    jsc_paths = [
+        "JSTests/",
+        "Source/JavaScriptCore/",
+        "Source/WTF/"
+        "Source/bmalloc/",
+    ]
+
+    group_to_paths_mapping = {
+        'jsc': jsc_paths,
+    }
+
+    def _changes_are_relevant(self, changed_files):
+        # In the default case, all patches are relevant
+        if self._options.group is None:
+            return True
+
+        patterns = self.group_to_paths_mapping[self._options.group]
+
+        for changed_file in changed_files:
+            for pattern in patterns:
+                if re.search(pattern, changed_file, re.IGNORECASE):
+                    return True
+
+        return False
+
+    def run(self, state):
+        _log.info("Checking relevance of patch")
+
+        change_list = self._tool.scm().changed_files()
+
+        if self._changes_are_relevant(change_list):
+            return True
+
+        _log.info("This patch does not have relevant changes.")
+        raise ScriptError(message="This patch does not have relevant changes.")
index 3ef47d0..f5e0def 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -46,6 +47,7 @@ class Options(object):
     email = make_option("--email", action="store", type="string", dest="email", help="Email address to use in ChangeLogs.")
     force_clean = make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)")
     git_commit = make_option("-g", "--git-commit", action="store", dest="git_commit", help="Operate on a local commit. If a range, the commits are squashed into one. <ref>.... includes the working copy changes. UPSTREAM can be used for the upstream/tracking branch.")
+    group = make_option("--group", action="store", dest="group", default=None, help="")
     local_commit = make_option("--local-commit", action="store_true", dest="local_commit", default=False, help="Make a local commit for each applied patch")
     non_interactive = make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible.")
     obsolete_patches = make_option("--no-obsolete", action="store_false", dest="obsolete_patches", default=True, help="Do not obsolete old patches before posting this one.")
index 75d72bc..b3fea9e 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -53,9 +54,14 @@ class RunTests(AbstractStep):
             Options.iterate_on_new_tests,
             Options.non_interactive,
             Options.quiet,
+            Options.group,
         ]
 
     def run(self, state):
+        if self._options.group == "jsc":
+            self._run_javascriptcore_tests()
+            return
+
         if self._options.iterate_on_new_tests:
             _log.info("Running run-webkit-tests on new tests")
             self._run_webkit_tests(self._options.iterate_on_new_tests)
@@ -143,3 +149,11 @@ class RunTests(AbstractStep):
             args.append("--iterations=%d" % iterate_on_new_tests)
 
         self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root)
+
+    def _run_javascriptcore_tests(self):
+        args = self._tool.deprecated_port().run_javascriptcore_tests_command(self._options.build_style)
+
+        results_directory = self._tool.port_factory.get().jsc_results_directory()
+        results_file_path = self._tool.filesystem.join(results_directory, "jsc_test_results.json")
+        args.append("--json-output=%s" % results_file_path)
+        self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root)
index 3000681..d4d9964 100644 (file)
@@ -40,7 +40,7 @@ class RunTestsTest(unittest.TestCase):
         tool = MockTool(log_executive=True)
         tool._deprecated_port.run_python_unittests_command = lambda: None
         tool._deprecated_port.run_perl_unittests_command = lambda: None
-        step = RunTests(tool, MockOptions(test=True, non_interactive=True, quiet=False, build_style="release", iterate_on_new_tests=0))
+        step = RunTests(tool, MockOptions(test=True, non_interactive=True, quiet=False, build_style="release", iterate_on_new_tests=0, group=None))
 
         if sys.platform != "cygwin":
             expected_logs = """Running bindings generation tests
index 7d94a13..8f667fa 100644 (file)
@@ -1,4 +1,5 @@
 # Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2017 Apple Inc. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
@@ -28,6 +29,7 @@
 
 import unittest
 
+from webkitpy.common.system.executive import ScriptError
 from webkitpy.common.system.outputcapture import OutputCapture
 from webkitpy.common.config.ports import DeprecatedPort
 from webkitpy.tool.mocktool import MockOptions, MockTool
@@ -38,6 +40,7 @@ from webkitpy.tool import steps
 class StepsTest(unittest.TestCase):
     def _step_options(self):
         options = MockOptions()
+        options.group = None
         options.non_interactive = True
         options.port = 'MOCK port'
         options.quiet = True
@@ -111,7 +114,7 @@ MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitpy'], cwd=/mock-checkout
 Running Perl unit tests
 MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitperl'], cwd=/mock-checkout
 Running JavaScriptCore tests
-MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests'], cwd=/mock-checkout
+MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests', '--no-fail-fast'], cwd=/mock-checkout
 Running bindings generation tests
 MOCK run_and_throw_if_fail: ['Tools/Scripts/run-bindings-tests'], cwd=/mock-checkout
 Running run-webkit-tests
@@ -133,10 +136,111 @@ MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitpy'], cwd=/mock-checkout
 Running Perl unit tests
 MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitperl'], cwd=/mock-checkout
 Running JavaScriptCore tests
-MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests'], cwd=/mock-checkout
+MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests', '--no-fail-fast'], cwd=/mock-checkout
 Running bindings generation tests
 MOCK run_and_throw_if_fail: ['Tools/Scripts/run-bindings-tests'], cwd=/mock-checkout
 Running run-webkit-tests
 MOCK run_and_throw_if_fail: ['Tools/Scripts/run-webkit-tests', '--debug', '--quiet'], cwd=/mock-checkout
 """
         OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_runtests_jsc(self):
+        mock_options = self._step_options()
+        mock_options.non_interactive = False
+        mock_options.build_style = "release"
+        mock_options.group = "jsc"
+        step = steps.RunTests(MockTool(log_executive=True), mock_options)
+        tool = MockTool(log_executive=True)
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.RunTests(tool, mock_options)
+        expected_logs = """MOCK run_command: ['perl', 'Tools/Scripts/webkit-build-directory', '--configuration', '--release', '--mac'], cwd=/mock-checkout
+MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests', '--no-fail-fast', '--release', '--json-output=/MOCK output of child process/jsc_test_results.json'], cwd=/mock-checkout
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_runtests_jsc_debug(self):
+        mock_options = self._step_options()
+        mock_options.non_interactive = False
+        mock_options.build_style = "debug"
+        mock_options.group = "jsc"
+        tool = MockTool(log_executive=True)
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.RunTests(tool, mock_options)
+        expected_logs = """MOCK run_command: ['perl', 'Tools/Scripts/webkit-build-directory', '--configuration', '--release', '--mac'], cwd=/mock-checkout
+MOCK run_and_throw_if_fail: ['Tools/Scripts/run-javascriptcore-tests', '--no-fail-fast', '--debug', '--json-output=/MOCK output of child process/jsc_test_results.json'], cwd=/mock-checkout
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_build_jsc_debug(self):
+        mock_options = self._step_options()
+        mock_options.non_interactive = False
+        mock_options.build_style = "debug"
+        mock_options.build = True
+        mock_options.architecture = True
+        mock_options.group = "jsc"
+        tool = MockTool(log_executive=True)
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.Build(tool, mock_options)
+        expected_logs = """Building WebKit
+MOCK run_and_throw_if_fail: ['Tools/Scripts/build-jsc', '--debug', 'ARCHS=True'], cwd=/mock-checkout, env={'LC_ALL': 'C', 'TERM': 'none', 'MOCK_ENVIRON_COPY': '1'}
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_build_jsc(self):
+        mock_options = self._step_options()
+        mock_options.non_interactive = False
+        mock_options.build_style = "release"
+        mock_options.build = True
+        mock_options.architecture = True
+        mock_options.group = "jsc"
+        tool = MockTool(log_executive=True)
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.Build(tool, mock_options)
+        expected_logs = """Building WebKit
+MOCK run_and_throw_if_fail: ['Tools/Scripts/build-jsc', '--release', 'ARCHS=True'], cwd=/mock-checkout, env={'LC_ALL': 'C', 'TERM': 'none', 'MOCK_ENVIRON_COPY': '1'}
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_patch_relevant(self):
+        self.maxDiff = None
+        mock_options = self._step_options()
+        tool = MockTool(log_executive=True)
+        tool.scm()._mockChangedFiles = ["JSTests/MockFile1", "ChangeLog"]
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.CheckPatchRelevance(tool, mock_options)
+        expected_logs = """Checking relevance of patch
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_patch_relevant_jsc(self):
+        self.maxDiff = None
+        mock_options = self._step_options()
+        mock_options.group = "jsc"
+        tool = MockTool(log_executive=True)
+        tool.scm()._mockChangedFiles = ["JSTests/MockFile1", "ChangeLog"]
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.CheckPatchRelevance(tool, mock_options)
+        expected_logs = """Checking relevance of patch
+"""
+        OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
+
+    def test_patch_not_relevant_jsc(self):
+        self.maxDiff = None
+        mock_options = self._step_options()
+        mock_options.group = "jsc"
+        tool = MockTool(log_executive=True)
+        tool.scm()._mockChangedFiles = ["Tools/ChangeLog", "Tools/Scripts/webkitpy/tool/steps/steps_unittest.py"]
+        # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
+        tool._deprecated_port = DeprecatedPort()
+        step = steps.CheckPatchRelevance(tool, mock_options)
+        expected_logs = """Checking relevance of patch
+This patch does not have relevant changes.
+"""
+        with self.assertRaises(ScriptError):
+            OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)