summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/tool
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@digia.com>2013-09-13 12:51:20 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-19 20:50:05 +0200
commitd441d6f39bb846989d95bcf5caf387b42414718d (patch)
treee367e64a75991c554930278175d403c072de6bb8 /Tools/Scripts/webkitpy/tool
parent0060b2994c07842f4c59de64b5e3e430525c4b90 (diff)
downloadqtwebkit-d441d6f39bb846989d95bcf5caf387b42414718d.tar.gz
Import Qt5x2 branch of QtWebkit for Qt 5.2
Importing a new snapshot of webkit. Change-Id: I2d01ad12cdc8af8cb015387641120a9d7ea5f10c Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
Diffstat (limited to 'Tools/Scripts/webkitpy/tool')
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/botinfo.py5
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/botinfo_unittest.py5
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py3
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py33
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/expectedfailures_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/feeders_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/flakytestreporter.py9
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/flakytestreporter_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/irc_command.py235
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/irc_command_unittest.py50
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/ircbot_unittest.py15
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader.py49
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader_unittest.py81
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/queueengine.py15
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/queueengine_unittest.py10
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/sheriff.py15
-rw-r--r--Tools/Scripts/webkitpy/tool/bot/sheriff_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/__init__.py7
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py8
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py6
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/adduserstogroups.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/analyzechangelog.py8
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/analyzechangelog_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/bugfortest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/bugsearch.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/chromechannels.py104
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/chromechannels_unittest.py99
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/commandtest.py1
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/download.py60
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/download_unittest.py17
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py111
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py47
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/findusers.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/gardenomatic.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/newcommitbot.py172
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/newcommitbot_unittest.py129
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/openbugs.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/perfalizer_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queries.py94
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queries_unittest.py23
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queues.py95
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queues_unittest.py111
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queuestest.py6
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/rebaseline.py28
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py96
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/roll.py74
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/roll_unittest.py63
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/sheriffbot.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py18
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/suggestnominations.py272
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/suggestnominations_unittest.py42
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/upload.py34
-rw-r--r--[-rwxr-xr-x]Tools/Scripts/webkitpy/tool/comments.py0
-rwxr-xr-xTools/Scripts/webkitpy/tool/gcovr1029
-rw-r--r--Tools/Scripts/webkitpy/tool/grammar_unittest.py5
-rw-r--r--[-rwxr-xr-x]Tools/Scripts/webkitpy/tool/main.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/mocktool.py5
-rw-r--r--Tools/Scripts/webkitpy/tool/mocktool_unittest.py6
-rw-r--r--Tools/Scripts/webkitpy/tool/multicommandtool.py24
-rw-r--r--Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py23
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/gardeningserver.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py4
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py23
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py8
-rw-r--r--Tools/Scripts/webkitpy/tool/servers/reflectionhandler_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/__init__.py5
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/applywatchlist_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/build.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/checkstyle.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py12
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py30
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py34
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/commit.py16
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/commit_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py (renamed from Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py)28
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py97
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/haslanded.py120
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py299
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/options.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/preparechangelog.py60
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py94
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py24
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/runtests.py48
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py23
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/steps_unittest.py9
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/suggestreviewers.py7
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/update.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/update_unittest.py26
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py77
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/validatechangelogs.py15
-rw-r--r--Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py15
97 files changed, 3161 insertions, 1333 deletions
diff --git a/Tools/Scripts/webkitpy/tool/bot/botinfo.py b/Tools/Scripts/webkitpy/tool/bot/botinfo.py
index b9fd938aa..11a3d40f4 100644
--- a/Tools/Scripts/webkitpy/tool/bot/botinfo.py
+++ b/Tools/Scripts/webkitpy/tool/bot/botinfo.py
@@ -29,11 +29,12 @@
# FIXME: We should consider hanging one of these off the tool object.
class BotInfo(object):
- def __init__(self, tool):
+ def __init__(self, tool, port_name):
self._tool = tool
+ self._port_name = port_name
def summary_text(self):
# bot_id is also stored on the options dictionary on the tool.
bot_id = self._tool.status_server.bot_id
bot_id_string = "Bot: %s " % (bot_id) if bot_id else ""
- return "%sPort: %s Platform: %s" % (bot_id_string, self._tool.port().name(), self._tool.platform.display_name())
+ return "%sPort: %s Platform: %s" % (bot_id_string, self._port_name, self._tool.platform.display_name())
diff --git a/Tools/Scripts/webkitpy/tool/bot/botinfo_unittest.py b/Tools/Scripts/webkitpy/tool/bot/botinfo_unittest.py
index 820ff559e..04861f452 100644
--- a/Tools/Scripts/webkitpy/tool/bot/botinfo_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/botinfo_unittest.py
@@ -26,11 +26,12 @@
# (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
+import unittest2 as unittest
from webkitpy.tool.bot.botinfo import BotInfo
from webkitpy.tool.mocktool import MockTool
from webkitpy.common.net.statusserver_mock import MockStatusServer
+from webkitpy.port.test import TestPort
class BotInfoTest(unittest.TestCase):
@@ -38,4 +39,4 @@ class BotInfoTest(unittest.TestCase):
def test_summary_text(self):
tool = MockTool()
tool.status_server = MockStatusServer("MockBotId")
- self.assertEqual(BotInfo(tool).summary_text(), "Bot: MockBotId Port: MockPort Platform: MockPlatform 1.0")
+ self.assertEqual(BotInfo(tool, 'port-name').summary_text(), "Bot: MockBotId Port: port-name Platform: MockPlatform 1.0")
diff --git a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py
index 491ba79da..a95c7b103 100644
--- a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py
+++ b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask.py
@@ -55,6 +55,7 @@ class CommitQueueTask(PatchAnalysisTask):
def _validate_changelog(self):
return self._run_command([
"validate-changelog",
+ "--check-oops",
"--non-interactive",
self._patch.id(),
],
@@ -88,7 +89,7 @@ class CommitQueueTask(PatchAnalysisTask):
# no one has set commit-queue- since we started working on the patch.)
if not self.validate():
return False
- # FIXME: We should understand why the land failure occured and retry if possible.
+ # FIXME: We should understand why the land failure occurred and retry if possible.
if not self._land():
return self.report_failure()
return True
diff --git a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py
index 2211b1de0..1eabde1b5 100644
--- a/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py
@@ -28,7 +28,7 @@
from datetime import datetime
import logging
-import unittest
+import unittest2 as unittest
from webkitpy.common.net import bugzilla
from webkitpy.common.net.layouttestresults import LayoutTestResults
@@ -124,6 +124,7 @@ class GoldenScriptError(ScriptError):
class CommitQueueTaskTest(unittest.TestCase):
def _run_through_task(self, commit_queue, expected_logs, expected_exception=None, expect_retry=False):
+ self.maxDiff = None
tool = MockTool(log_executive=True)
patch = tool.bugs.fetch_attachment(10000)
task = CommitQueueTask(commit_queue, patch)
@@ -140,7 +141,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -160,7 +161,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -218,7 +219,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_failed: failure_message='ChangeLog did not pass validation' script_error='MOCK validate failure' patch='10000'
"""
self._run_through_task(commit_queue, expected_logs, GoldenScriptError)
@@ -237,7 +238,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='10000'
@@ -261,7 +262,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='10000'
@@ -280,7 +281,7 @@ command_failed: failure_message='Unable to build without patch' script_error='MO
ScriptError("MOCK tests failure"),
])
# CommitQueueTask will only report flaky tests if we successfully parsed
- # results.html and returned a LayoutTestResults object, so we fake one.
+ # results.json and returned a LayoutTestResults object, so we fake one.
commit_queue.test_results = lambda: LayoutTestResults([])
expected_logs = """run_webkit_patch: ['clean']
command_passed: success_message='Cleaned working directory' patch='10000'
@@ -288,7 +289,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -322,7 +323,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -358,7 +359,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -372,7 +373,7 @@ command_failed: failure_message='Patch does not pass tests' script_error='MOCK t
patch = tool.bugs.fetch_attachment(10000)
task = CommitQueueTask(commit_queue, patch)
success = OutputCapture().assert_outputs(self, task.run, expected_logs=expected_logs)
- self.assertEqual(success, False)
+ self.assertFalse(success)
def test_test_failure(self):
commit_queue = MockCommitQueue([
@@ -390,7 +391,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -429,7 +430,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -472,7 +473,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -511,7 +512,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
@@ -545,7 +546,7 @@ run_webkit_patch: ['update']
command_passed: success_message='Updated working directory' patch='10000'
run_webkit_patch: ['apply-attachment', '--no-update', '--non-interactive', 10000]
command_passed: success_message='Applied patch' patch='10000'
-run_webkit_patch: ['validate-changelog', '--non-interactive', 10000]
+run_webkit_patch: ['validate-changelog', '--check-oops', '--non-interactive', 10000]
command_passed: success_message='ChangeLog validated' patch='10000'
run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both']
command_passed: success_message='Built patch' patch='10000'
diff --git a/Tools/Scripts/webkitpy/tool/bot/expectedfailures_unittest.py b/Tools/Scripts/webkitpy/tool/bot/expectedfailures_unittest.py
index 3cee3f059..b639856f3 100644
--- a/Tools/Scripts/webkitpy/tool/bot/expectedfailures_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/expectedfailures_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.tool.bot.expectedfailures import ExpectedFailures
diff --git a/Tools/Scripts/webkitpy/tool/bot/feeders_unittest.py b/Tools/Scripts/webkitpy/tool/bot/feeders_unittest.py
index 9d0b71408..b70a6371e 100644
--- a/Tools/Scripts/webkitpy/tool/bot/feeders_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/feeders_unittest.py
@@ -27,7 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from datetime import datetime
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.thirdparty.mock import Mock
@@ -40,7 +40,7 @@ class FeedersTest(unittest.TestCase):
feeder = CommitQueueFeeder(MockTool())
expected_logs = """Warning, attachment 10001 on bug 50000 has invalid committer (non-committer@example.com)
Warning, attachment 10001 on bug 50000 has invalid committer (non-committer@example.com)
-MOCK setting flag 'commit-queue' to '-' on attachment '10001' with comment 'Rejecting attachment 10001 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/Tools/Scripts/webkitpy/common/config/committers.py.
+MOCK setting flag 'commit-queue' to '-' on attachment '10001' with comment 'Rejecting attachment 10001 from commit-queue.\n\nnon-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/Tools/Scripts/webkitpy/common/config/committers.py.
- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
diff --git a/Tools/Scripts/webkitpy/tool/bot/flakytestreporter.py b/Tools/Scripts/webkitpy/tool/bot/flakytestreporter.py
index 7be4a4a30..b9fdf669c 100644
--- a/Tools/Scripts/webkitpy/tool/bot/flakytestreporter.py
+++ b/Tools/Scripts/webkitpy/tool/bot/flakytestreporter.py
@@ -42,7 +42,8 @@ class FlakyTestReporter(object):
def __init__(self, tool, bot_name):
self._tool = tool
self._bot_name = bot_name
- self._bot_info = BotInfo(tool)
+ # FIXME: Use the real port object
+ self._bot_info = BotInfo(tool, tool.deprecated_port().name())
def _author_emails_for_test(self, flaky_test):
test_path = path_for_layout_test(flaky_test)
@@ -139,12 +140,8 @@ If you would like to track this test fix with another bug, please close this bug
bug = self._tool.bugs.fetch_bug(bug.duplicate_of())
return bug
- # Maybe this logic should move into Bugzilla? a reopen=True arg to post_comment?
def _update_bug_for_flaky_test(self, bug, latest_flake_message):
- if bug.is_closed():
- self._tool.bugs.reopen_bug(bug.id(), latest_flake_message)
- else:
- self._tool.bugs.post_comment_to_bug(bug.id(), latest_flake_message)
+ self._tool.bugs.post_comment_to_bug(bug.id(), latest_flake_message)
# This method is needed because our archive paths include a leading tmp/layout-test-results
def _find_in_archive(self, path, archive):
diff --git a/Tools/Scripts/webkitpy/tool/bot/flakytestreporter_unittest.py b/Tools/Scripts/webkitpy/tool/bot/flakytestreporter_unittest.py
index 48c511281..5e30a66aa 100644
--- a/Tools/Scripts/webkitpy/tool/bot/flakytestreporter_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/flakytestreporter_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.config.committers import Committer
from webkitpy.common.system.filesystem_mock import MockFileSystem
@@ -46,7 +46,7 @@ class MockCommitInfo(object):
def author(self):
# It's definitely possible to have commits with authors who
- # are not in our committers.py list.
+ # are not in our contributors.json list.
if not self._author_email:
return None
return Committer("Mock Committer", self._author_email)
diff --git a/Tools/Scripts/webkitpy/tool/bot/irc_command.py b/Tools/Scripts/webkitpy/tool/bot/irc_command.py
index 1c061a8db..9b9915769 100644
--- a/Tools/Scripts/webkitpy/tool/bot/irc_command.py
+++ b/Tools/Scripts/webkitpy/tool/bot/irc_command.py
@@ -33,6 +33,7 @@ import re
from webkitpy.common.config import irc as config_irc
from webkitpy.common.config import urls
from webkitpy.common.config.committers import CommitterList
+from webkitpy.common.net.web import Web
from webkitpy.common.system.executive import ScriptError
from webkitpy.tool.bot.queueengine import TerminateQueue
from webkitpy.tool.grammar import join_with_separators
@@ -48,19 +49,121 @@ def _post_error_and_check_for_bug_url(tool, nicks_string, exception):
# FIXME: Merge with Command?
class IRCCommand(object):
+ usage_string = None
+ help_string = None
+
+ def execute(self, nick, args, tool, sheriff):
+ raise NotImplementedError("subclasses must implement")
+
+ @classmethod
+ def usage(cls, nick):
+ return "%s: Usage: %s" % (nick, cls.usage_string)
+
+ @classmethod
+ def help(cls, nick):
+ return "%s: %s" % (nick, cls.help_string)
+
+
+class CreateBug(IRCCommand):
+ usage_string = "create-bug BUG_TITLE"
+ help_string = "Creates a Bugzilla bug with the given title."
+
+ def execute(self, nick, args, tool, sheriff):
+ if not args:
+ return self.usage(nick)
+
+ bug_title = " ".join(args)
+ bug_description = "%s\nRequested by %s on %s." % (bug_title, nick, config_irc.channel)
+
+ # There happens to be a committers list hung off of Bugzilla, so
+ # re-using that one makes things easiest for now.
+ requester = tool.bugs.committers.contributor_by_irc_nickname(nick)
+ requester_email = requester.bugzilla_email() if requester else None
+
+ try:
+ bug_id = tool.bugs.create_bug(bug_title, bug_description, cc=requester_email, assignee=requester_email)
+ bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
+ return "%s: Created bug: %s" % (nick, bug_url)
+ except Exception, e:
+ return "%s: Failed to create bug:\n%s" % (nick, e)
+
+
+class Help(IRCCommand):
+ usage_string = "help [COMMAND]"
+ help_string = "Provides help on my individual commands."
+
+ def execute(self, nick, args, tool, sheriff):
+ if args:
+ for command_name in args:
+ if command_name in commands:
+ self._post_command_help(nick, tool, commands[command_name])
+ else:
+ tool.irc().post("%s: Available commands: %s" % (nick, ", ".join(sorted(visible_commands.keys()))))
+ tool.irc().post('%s: Type "%s: help COMMAND" for help on my individual commands.' % (nick, sheriff.name()))
+
+ def _post_command_help(self, nick, tool, command):
+ tool.irc().post(command.usage(nick))
+ tool.irc().post(command.help(nick))
+ aliases = " ".join(sorted(filter(lambda alias: commands[alias] == command and alias not in visible_commands, commands)))
+ if aliases:
+ tool.irc().post("%s: Aliases: %s" % (nick, aliases))
+
+
+class Hi(IRCCommand):
+ usage_string = "hi"
+ help_string = "Responds with hi."
+
+ def execute(self, nick, args, tool, sheriff):
+ if len(args) and re.match(sheriff.name() + r'_*\s*!\s*', ' '.join(args)):
+ return "%s: hi %s!" % (nick, nick)
+ quips = tool.bugs.quips()
+ quips.append('"Only you can prevent forest fires." -- Smokey the Bear')
+ return random.choice(quips)
+
+
+class PingPong(IRCCommand):
+ usage_string = "ping"
+ help_string = "Responds with pong."
+
def execute(self, nick, args, tool, sheriff):
- raise NotImplementedError, "subclasses must implement"
+ return nick + ": pong"
+
+
+class YouThere(IRCCommand):
+ usage_string = "yt?"
+ help_string = "Responds with yes."
+
+ def execute(self, nick, args, tool, sheriff):
+ return "%s: yes" % nick
class Restart(IRCCommand):
+ usage_string = "restart"
+ help_string = "Restarts sherrifbot. Will update its WebKit checkout, and re-join the channel momentarily."
+
def execute(self, nick, args, tool, sheriff):
tool.irc().post("Restarting...")
raise TerminateQueue()
+class RollChromiumDEPS(IRCCommand):
+ usage_string = "roll-chromium-deps REVISION"
+ help_string = "Rolls WebKit's Chromium DEPS to the given revision???"
+
+ def execute(self, nick, args, tool, sheriff):
+ if not len(args):
+ return self.usage(nick)
+ tool.irc().post("%s: Will roll Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Rolling Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Rolled Chromium DEPS to %s" % (nick, ' '.join(args)))
+ tool.irc().post("%s: Thank You" % nick)
+
+
class Rollout(IRCCommand):
- def _extract_revisions(self, arg):
+ usage_string = "rollout SVN_REVISION [SVN_REVISIONS] REASON"
+ help_string = "Opens a rollout bug, CCing author + reviewer, and attaching the reverse-diff of the given revisions marked as commit-queue=?."
+ def _extract_revisions(self, arg):
revision_list = []
possible_revisions = arg.split(",")
for revision in possible_revisions:
@@ -110,15 +213,28 @@ class Rollout(IRCCommand):
return ", ".join(target_nicks)
def _update_working_copy(self, tool):
- tool.scm().ensure_clean_working_directory(force_clean=True)
- tool.executive.run_and_throw_if_fail(tool.port().update_webkit_command(), quiet=True, cwd=tool.scm().checkout_root)
+ tool.scm().discard_local_changes()
+ tool.executive.run_and_throw_if_fail(tool.deprecated_port().update_webkit_command(), quiet=True, cwd=tool.scm().checkout_root)
+
+ def _check_diff_failure(self, error_log, tool):
+ if not error_log:
+ return None
+
+ revert_failure_message_start = error_log.find("Failed to apply reverse diff for revision")
+ if revert_failure_message_start == -1:
+ return None
+
+ lines = error_log[revert_failure_message_start:].split('\n')[1:]
+ files = itertools.takewhile(lambda line: tool.filesystem.exists(tool.scm().absolute_path(line)), lines)
+ if files:
+ return "Failed to apply reverse diff for file(s): %s" % ", ".join(files)
+ return None
def execute(self, nick, args, tool, sheriff):
svn_revision_list, rollout_reason = self._parse_args(args)
if (not svn_revision_list or not rollout_reason):
- # return is equivalent to an irc().post(), but makes for easier unit testing.
- return "%s: Usage: rollout SVN_REVISION [SVN_REVISIONS] REASON" % nick
+ return self.usage(nick)
revision_urls_string = join_with_separators([urls.view_revision_url(revision) for revision in svn_revision_list])
tool.irc().post("%s: Preparing rollout for %s ..." % (nick, revision_urls_string))
@@ -137,109 +253,60 @@ class Rollout(IRCCommand):
tool.irc().post("%s: Created rollout: %s" % (nicks_string, bug_url))
except ScriptError, e:
tool.irc().post("%s: Failed to create rollout patch:" % nicks_string)
+ diff_failure = self._check_diff_failure(e.output, tool)
+ if diff_failure:
+ return "%s: %s" % (nicks_string, diff_failure)
_post_error_and_check_for_bug_url(tool, nicks_string, e)
-class RollChromiumDEPS(IRCCommand):
- def _parse_args(self, args):
- if not args:
- return
- revision = args[0].lstrip("r")
- if not revision.isdigit():
- return
- return revision
-
- def execute(self, nick, args, tool, sheriff):
- revision = self._parse_args(args)
-
- roll_target = "r%s" % revision if revision else "last-known good revision"
- tool.irc().post("%s: Rolling Chromium DEPS to %s" % (nick, roll_target))
-
- try:
- bug_id = sheriff.post_chromium_deps_roll(revision, roll_target)
- bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
- tool.irc().post("%s: Created DEPS roll: %s" % (nick, bug_url))
- except ScriptError, e:
- match = re.search(r"Current Chromium DEPS revision \d+ is newer than \d+\.", e.output)
- if match:
- tool.irc().post("%s: %s" % (nick, match.group(0)))
- return
- tool.irc().post("%s: Failed to create DEPS roll:" % nick)
- _post_error_and_check_for_bug_url(tool, nick, e)
-
-
-class Help(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- return "%s: Available commands: %s" % (nick, ", ".join(sorted(visible_commands.keys())))
+class Whois(IRCCommand):
+ usage_string = "whois SEARCH_STRING"
+ help_string = "Searches known contributors and returns any matches with irc, email and full name. Wild card * permitted."
+ def _full_record_and_nick(self, contributor):
+ result = ''
-class Hi(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- quips = tool.bugs.quips()
- quips.append('"Only you can prevent forest fires." -- Smokey the Bear')
- return random.choice(quips)
+ if contributor.irc_nicknames:
+ result += ' (:%s)' % ', :'.join(contributor.irc_nicknames)
+ if contributor.can_review:
+ result += ' (r)'
+ elif contributor.can_commit:
+ result += ' (c)'
-class Whois(IRCCommand):
- def _nick_or_full_record(self, contributor):
- if contributor.irc_nicknames:
- return ', '.join(contributor.irc_nicknames)
- return unicode(contributor)
+ return unicode(contributor) + result
def execute(self, nick, args, tool, sheriff):
- if len(args) != 1:
- return "%s: Usage: whois SEARCH_STRING" % nick
- search_string = args[0]
+ if not args:
+ return self.usage(nick)
+ search_string = unicode(" ".join(args))
# FIXME: We should get the ContributorList off the tool somewhere.
contributors = CommitterList().contributors_by_search_string(search_string)
if not contributors:
- return "%s: Sorry, I don't know any contributors matching '%s'." % (nick, search_string)
+ return unicode("%s: Sorry, I don't know any contributors matching '%s'.") % (nick, search_string)
if len(contributors) > 5:
- return "%s: More than 5 contributors match '%s', could you be more specific?" % (nick, search_string)
+ return unicode("%s: More than 5 contributors match '%s', could you be more specific?") % (nick, search_string)
if len(contributors) == 1:
contributor = contributors[0]
if not contributor.irc_nicknames:
- return "%s: %s hasn't told me their nick. Boo hoo :-(" % (nick, contributor)
- if contributor.emails and search_string.lower() not in map(lambda email: email.lower(), contributor.emails):
- formattedEmails = ', '.join(contributor.emails)
- return "%s: %s is %s (%s). Why do you ask?" % (nick, search_string, self._nick_or_full_record(contributor), formattedEmails)
- else:
- return "%s: %s is %s. Why do you ask?" % (nick, search_string, self._nick_or_full_record(contributor))
- contributor_nicks = map(self._nick_or_full_record, contributors)
+ return unicode("%s: %s hasn't told me their nick. Boo hoo :-(") % (nick, contributor)
+ return unicode("%s: %s is %s. Why do you ask?") % (nick, search_string, self._full_record_and_nick(contributor))
+ contributor_nicks = map(self._full_record_and_nick, contributors)
contributors_string = join_with_separators(contributor_nicks, only_two_separator=" or ", last_separator=', or ')
- return "%s: I'm not sure who you mean? %s could be '%s'." % (nick, contributors_string, search_string)
-
-
-class CreateBug(IRCCommand):
- def execute(self, nick, args, tool, sheriff):
- if not args:
- return "%s: Usage: create-bug BUG_TITLE" % nick
-
- bug_title = " ".join(args)
- bug_description = "%s\nRequested by %s on %s." % (bug_title, nick, config_irc.channel)
-
- # There happens to be a committers list hung off of Bugzilla, so
- # re-using that one makes things easiest for now.
- requester = tool.bugs.committers.contributor_by_irc_nickname(nick)
- requester_email = requester.bugzilla_email() if requester else None
-
- try:
- bug_id = tool.bugs.create_bug(bug_title, bug_description, cc=requester_email, assignee=requester_email)
- bug_url = tool.bugs.bug_url_for_bug_id(bug_id)
- return "%s: Created bug: %s" % (nick, bug_url)
- except Exception, e:
- return "%s: Failed to create bug:\n%s" % (nick, e)
+ return unicode("%s: I'm not sure who you mean? %s could be '%s'.") % (nick, contributors_string, search_string)
# FIXME: Lame. We should have an auto-registering CommandCenter.
visible_commands = {
+ "create-bug": CreateBug,
"help": Help,
"hi": Hi,
+ "ping": PingPong,
"restart": Restart,
+ "roll-chromium-deps": RollChromiumDEPS,
"rollout": Rollout,
"whois": Whois,
- "create-bug": CreateBug,
- "roll-chromium-deps": RollChromiumDEPS,
+ "yt?": YouThere,
}
# Add revert as an "easter egg" command. Why?
@@ -248,3 +315,5 @@ visible_commands = {
# people to use and it seems silly to have them hunt around for "rollout" instead.
commands = visible_commands.copy()
commands["revert"] = Rollout
+# "hello" Alias for "hi" command for the purposes of testing aliases
+commands["hello"] = Hi
diff --git a/Tools/Scripts/webkitpy/tool/bot/irc_command_unittest.py b/Tools/Scripts/webkitpy/tool/bot/irc_command_unittest.py
index e307e6ea9..1bf26a158 100644
--- a/Tools/Scripts/webkitpy/tool/bot/irc_command_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/irc_command_unittest.py
@@ -26,12 +26,15 @@
# (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
+import os
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.bot.irc_command import *
from webkitpy.tool.mocktool import MockTool
+from webkitpy.common.net.web_mock import MockWeb
from webkitpy.common.system.executive_mock import MockExecutive
+from webkitpy.common.system.filesystem_mock import MockFileSystem
class IRCCommandTest(unittest.TestCase):
@@ -39,21 +42,23 @@ class IRCCommandTest(unittest.TestCase):
whois = Whois()
self.assertEqual("tom: Usage: whois SEARCH_STRING",
whois.execute("tom", [], None, None))
- self.assertEqual("tom: Usage: whois SEARCH_STRING",
+ self.assertEqual('tom: Adam Barth is "Adam Barth" <abarth@webkit.org> (:abarth) (r). Why do you ask?',
whois.execute("tom", ["Adam", "Barth"], None, None))
self.assertEqual("tom: Sorry, I don't know any contributors matching 'unknown@example.com'.",
whois.execute("tom", ["unknown@example.com"], None, None))
- self.assertEqual("tom: tonyg@chromium.org is tonyg-cr. Why do you ask?",
+ self.assertEqual('tom: tonyg@chromium.org is "Tony Gentilcore" <tonyg@chromium.org> (:tonyg-cr) (r). Why do you ask?',
whois.execute("tom", ["tonyg@chromium.org"], None, None))
- self.assertEqual("tom: TonyG@Chromium.org is tonyg-cr. Why do you ask?",
+ self.assertEqual('tom: TonyG@Chromium.org is "Tony Gentilcore" <tonyg@chromium.org> (:tonyg-cr) (r). Why do you ask?',
whois.execute("tom", ["TonyG@Chromium.org"], None, None))
- self.assertEqual("tom: rniwa is rniwa (rniwa@webkit.org). Why do you ask?",
+ self.assertEqual('tom: rniwa is "Ryosuke Niwa" <rniwa@webkit.org> (:rniwa) (r). Why do you ask?',
whois.execute("tom", ["rniwa"], None, None))
- self.assertEqual("tom: lopez is xan (xan.lopez@gmail.com, xan@gnome.org, xan@webkit.org, xlopez@igalia.com). Why do you ask?",
+ self.assertEqual('tom: lopez is "Xan Lopez" <xan.lopez@gmail.com> (:xan) (r). Why do you ask?',
whois.execute("tom", ["lopez"], None, None))
+ self.assertEqual(u'tom: Osztrogon\u00e1c is "Csaba Osztrogon\u00e1c" <ossy@webkit.org> (:ossy) (r). Why do you ask?',
+ whois.execute("tom", [u'Osztrogon\u00e1c'], None, None))
self.assertEqual('tom: "Vicki Murley" <vicki@apple.com> hasn\'t told me their nick. Boo hoo :-(',
whois.execute("tom", ["vicki@apple.com"], None, None))
- self.assertEqual('tom: I\'m not sure who you mean? gavinp or gbarra could be \'Gavin\'.',
+ self.assertEqual('tom: I\'m not sure who you mean? "Gavin Peters" <gavinp@chromium.org> (:gavinp) (c) or "Gavin Barraclough" <barraclough@apple.com> (:gbarra) (r) could be \'Gavin\'.',
whois.execute("tom", ["Gavin"], None, None))
self.assertEqual('tom: More than 5 contributors match \'david\', could you be more specific?',
whois.execute("tom", ["david"], None, None))
@@ -77,11 +82,6 @@ class IRCCommandTest(unittest.TestCase):
self.assertEqual("tom: Failed to create bug:\nException from bugzilla!",
create_bug.execute("tom", example_args, tool, None))
- def test_roll_chromium_deps(self):
- roll = RollChromiumDEPS()
- self.assertEqual(None, roll._parse_args([]))
- self.assertEqual("1234", roll._parse_args(["1234"]))
-
def test_rollout_updates_working_copy(self):
rollout = Rollout()
tool = MockTool()
@@ -114,4 +114,30 @@ class IRCCommandTest(unittest.TestCase):
self.assertEqual("tom: Usage: rollout SVN_REVISION [SVN_REVISIONS] REASON",
rollout.execute("tom", [], None, None))
+ tool = MockTool()
+ tool.filesystem.files["/mock-checkout/test/file/one"] = ""
+ tool.filesystem.files["/mock-checkout/test/file/two"] = ""
+ self.assertEqual("Failed to apply reverse diff for file(s): test/file/one, test/file/two",
+ rollout._check_diff_failure("""
+Preparing rollout for bug 123456.
+Updating working directory
+Failed to apply reverse diff for revision 123456 because of the following conflicts:
+test/file/one
+test/file/two
+Failed to apply reverse diff for revision 123456 because of the following conflicts:
+test/file/one
+test/file/two
+Updating OpenSource
+Current branch master is up to date.
+ """, tool))
+ self.assertEqual(None, rollout._check_diff_failure("""
+Preparing rollout for bug 123456.
+Updating working directory
+Some other error report involving file paths:
+test/file/one
+test/file/two
+Updating OpenSource
+Current branch master is up to date.
+ """, tool))
+
# FIXME: We need a better way to test IRCCommands which call tool.irc().post()
diff --git a/Tools/Scripts/webkitpy/tool/bot/ircbot_unittest.py b/Tools/Scripts/webkitpy/tool/bot/ircbot_unittest.py
index f96b7b6b5..7e1767023 100644
--- a/Tools/Scripts/webkitpy/tool/bot/ircbot_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/ircbot_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
import random
from webkitpy.common.system.outputcapture import OutputCapture
@@ -88,8 +88,11 @@ class IRCBotTest(unittest.TestCase):
OutputCapture().assert_outputs(self, run, args=["hi"], expected_logs=expected_logs)
def test_help(self):
- expected_logs = "MOCK: irc.post: mock_nick: Available commands: create-bug, help, hi, restart, roll-chromium-deps, rollout, whois\n"
+ expected_logs = 'MOCK: irc.post: mock_nick: Available commands: create-bug, help, hi, ping, restart, roll-chromium-deps, rollout, whois, yt?\nMOCK: irc.post: mock_nick: Type "mock-sheriff-bot: help COMMAND" for help on my individual commands.\n'
OutputCapture().assert_outputs(self, run, args=["help"], expected_logs=expected_logs)
+ expected_logs = 'MOCK: irc.post: mock_nick: Usage: hi\nMOCK: irc.post: mock_nick: Responds with hi.\nMOCK: irc.post: mock_nick: Aliases: hello\n'
+ OutputCapture().assert_outputs(self, run, args=["help hi"], expected_logs=expected_logs)
+ OutputCapture().assert_outputs(self, run, args=["help hello"], expected_logs=expected_logs)
def test_restart(self):
expected_logs = "MOCK: irc.post: Restarting...\n"
@@ -103,14 +106,6 @@ class IRCBotTest(unittest.TestCase):
expected_logs = "MOCK: irc.post: mock_nick: Preparing rollout for http://trac.webkit.org/changeset/21654 ...\nMOCK: irc.post: mock_nick, abarth, darin, eseidel: Created rollout: http://example.com/36936\n"
OutputCapture().assert_outputs(self, run, args=["revert 21654 This patch broke the world"], expected_logs=expected_logs)
- def test_roll_chromium_deps(self):
- expected_logs = "MOCK: irc.post: mock_nick: Rolling Chromium DEPS to r21654\nMOCK: irc.post: mock_nick: Created DEPS roll: http://example.com/36936\n"
- OutputCapture().assert_outputs(self, run, args=["roll-chromium-deps 21654"], expected_logs=expected_logs)
-
- def test_roll_chromium_deps_to_lkgr(self):
- expected_logs = "MOCK: irc.post: mock_nick: Rolling Chromium DEPS to last-known good revision\nMOCK: irc.post: mock_nick: Created DEPS roll: http://example.com/36936\n"
- OutputCapture().assert_outputs(self, run, args=["roll-chromium-deps"], expected_logs=expected_logs)
-
def test_multi_rollout(self):
expected_logs = "MOCK: irc.post: mock_nick: Preparing rollout for http://trac.webkit.org/changeset/21654, http://trac.webkit.org/changeset/21655, and http://trac.webkit.org/changeset/21656 ...\nMOCK: irc.post: mock_nick, abarth, darin, eseidel: Created rollout: http://example.com/36936\n"
OutputCapture().assert_outputs(self, run, args=["rollout 21654 21655 21656 This 21654 patch broke the world"], expected_logs=expected_logs)
diff --git a/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader.py b/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader.py
index 4e09f896f..a9e53ddb2 100644
--- a/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader.py
+++ b/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 Google Inc. All rights reserved.
+# Copyright (c) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -35,31 +35,32 @@ from webkitpy.tool.steps.runtests import RunTests
_log = logging.getLogger(__name__)
+# FIXME: This class no longer has a clear purpose, and should probably
+# be made part of Port, or renamed to LayoutTestResultsArchiver or something more fitting?
class LayoutTestResultsReader(object):
- def __init__(self, tool, archive_directory):
- self._tool = tool
+ def __init__(self, host, results_directory, archive_directory):
+ self._host = host
+ self._results_directory = results_directory
self._archive_directory = archive_directory
# FIXME: This exists for mocking, but should instead be mocked via
- # tool.filesystem.read_text_file. They have different error handling at the moment.
+ # host.filesystem.read_text_file. They have different error handling at the moment.
def _read_file_contents(self, path):
try:
- return self._tool.filesystem.read_text_file(path)
+ return self._host.filesystem.read_text_file(path)
except IOError, e: # File does not exist or can't be read.
return None
# FIXME: This logic should move to the port object.
def _create_layout_test_results(self):
- results_path = self._tool.port().layout_tests_results_path()
+ results_path = self._host.filesystem.join(self._results_directory, "full_results.json")
results_html = self._read_file_contents(results_path)
if not results_html:
return None
return LayoutTestResults.results_from_string(results_html)
def _create_unit_test_results(self):
- results_path = self._tool.port().unit_tests_results_path()
- if not results_path:
- return None
+ results_path = self._host.filesystem.join(self._results_directory, "webkit_unit_tests_output.xml")
results_xml = self._read_file_contents(results_path)
if not results_xml:
return None
@@ -69,36 +70,28 @@ class LayoutTestResultsReader(object):
layout_test_results = self._create_layout_test_results()
unit_test_results = self._create_unit_test_results()
if layout_test_results:
- # FIXME: We should not have to set failure_limit_count, but we
- # do until run-webkit-tests can be updated save off the value
- # of --exit-after-N-failures in results.html/results.json.
- # https://bugs.webkit.org/show_bug.cgi?id=58481
+ # FIXME: This is used to detect if we had N failures due to
+ # N tests failing, or if we hit the "exit-after-n-failures" limit.
+ # These days we could just check for the "interrupted" key in results.json instead!
layout_test_results.set_failure_limit_count(RunTests.NON_INTERACTIVE_FAILURE_LIMIT_COUNT)
if unit_test_results:
layout_test_results.add_unit_test_failures(unit_test_results)
return layout_test_results
- def _results_directory(self):
- results_path = self._tool.port().layout_tests_results_path()
- # FIXME: This is wrong in two ways:
- # 1. It assumes that results.html is at the top level of the results tree.
- # 2. This uses the "old" ports.py infrastructure instead of the new layout_tests/port
- # which will not support Chromium. However the new arch doesn't work with old-run-webkit-tests
- # so we have to use this for now.
- return self._tool.filesystem.dirname(results_path)
-
def archive(self, patch):
- results_directory = self._results_directory()
- results_name, _ = self._tool.filesystem.splitext(self._tool.filesystem.basename(results_directory))
+ filesystem = self._host.filesystem
+ workspace = self._host.workspace
+ results_directory = self._results_directory
+ results_name, _ = filesystem.splitext(filesystem.basename(results_directory))
# Note: We name the zip with the bug_id instead of patch_id to match work_item_log_path().
- zip_path = self._tool.workspace.find_unused_filename(self._archive_directory, "%s-%s" % (patch.bug_id(), results_name), "zip")
+ zip_path = workspace.find_unused_filename(self._archive_directory, "%s-%s" % (patch.bug_id(), results_name), "zip")
if not zip_path:
return None
- if not self._tool.filesystem.isdir(results_directory):
+ if not filesystem.isdir(results_directory):
_log.info("%s does not exist, not archiving." % results_directory)
return None
- archive = self._tool.workspace.create_zip(zip_path, results_directory)
+ archive = workspace.create_zip(filesystem.abspath(zip_path), filesystem.abspath(results_directory))
# Remove the results directory to prevent http logs, etc. from getting huge between runs.
# We could have create_zip remove the original, but this is more explicit.
- self._tool.filesystem.rmtree(results_directory)
+ filesystem.rmtree(results_directory)
return archive
diff --git a/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader_unittest.py b/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader_unittest.py
index 6079632bd..c779bb8df 100644
--- a/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/layouttestresultsreader_unittest.py
@@ -26,32 +26,33 @@
# (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
+import unittest2 as unittest
from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.net.layouttestresults import LayoutTestResults
-from webkitpy.tool.bot.layouttestresultsreader import *
-from webkitpy.tool.mocktool import MockTool
+from webkitpy.common.host_mock import MockHost
+
+from .layouttestresultsreader import LayoutTestResultsReader
class LayoutTestResultsReaderTest(unittest.TestCase):
def test_missing_layout_test_results(self):
- tool = MockTool()
- reader = LayoutTestResultsReader(tool, "/var/logs")
+ host = MockHost()
+ reader = LayoutTestResultsReader(host, "/mock-results", "/var/logs")
layout_tests_results_path = '/mock-results/full_results.json'
unit_tests_results_path = '/mock-results/webkit_unit_tests_output.xml'
- tool.filesystem = MockFileSystem({layout_tests_results_path: None,
+ host.filesystem = MockFileSystem({layout_tests_results_path: None,
unit_tests_results_path: None})
# Make sure that our filesystem mock functions as we expect.
- self.assertRaises(IOError, tool.filesystem.read_text_file, layout_tests_results_path)
- self.assertRaises(IOError, tool.filesystem.read_text_file, unit_tests_results_path)
- # layout_test_results shouldn't raise even if the results.html file is missing.
- self.assertEqual(reader.results(), None)
+ self.assertRaises(IOError, host.filesystem.read_text_file, layout_tests_results_path)
+ self.assertRaises(IOError, host.filesystem.read_text_file, unit_tests_results_path)
+ # layout_test_results shouldn't raise even if the results.json file is missing.
+ self.assertIsNone(reader.results())
def test_create_unit_test_results(self):
- tool = MockTool()
- reader = LayoutTestResultsReader(tool, "/var/logs")
+ host = MockHost()
+ reader = LayoutTestResultsReader(host, "/mock-results", "/var/logs")
unit_tests_results_path = '/mock-results/webkit_unit_tests_output.xml'
no_failures_xml = """<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="0" disabled="0" errors="0" time="11.35" name="AllTests">
@@ -61,45 +62,59 @@ class LayoutTestResultsReaderTest(unittest.TestCase):
<testcase name="CrashIfSettingUnsetRowIndex" status="run" time="0.123" classname="RenderTableCellDeathTest" />
</testsuite>
</testsuites>"""
- tool.filesystem = MockFileSystem({unit_tests_results_path: no_failures_xml})
+ host.filesystem = MockFileSystem({unit_tests_results_path: no_failures_xml})
self.assertEqual(reader._create_unit_test_results(), [])
def test_missing_unit_test_results_path(self):
- tool = MockTool()
- tool.port().unit_tests_results_path = lambda: None
- reader = LayoutTestResultsReader(tool, "/var/logs")
+ host = MockHost()
+ reader = LayoutTestResultsReader(host, "/mock-results", "/var/logs")
reader._create_layout_test_results = lambda: LayoutTestResults([])
+ reader._create_unit_test_results = lambda: None
# layout_test_results shouldn't raise even if the unit tests xml file is missing.
- self.assertNotEquals(reader.results(), None)
+ self.assertIsNotNone(reader.results(), None)
self.assertEqual(reader.results().failing_tests(), [])
def test_layout_test_results(self):
- reader = LayoutTestResultsReader(MockTool(), "/var/logs")
+ reader = LayoutTestResultsReader(MockHost(), "/mock-results", "/var/logs")
reader._read_file_contents = lambda path: None
- self.assertEqual(reader.results(), None)
+ self.assertIsNone(reader.results())
reader._read_file_contents = lambda path: ""
- self.assertEqual(reader.results(), None)
+ self.assertIsNone(reader.results())
reader._create_layout_test_results = lambda: LayoutTestResults([])
results = reader.results()
- self.assertNotEquals(results, None)
+ self.assertIsNotNone(results)
self.assertEqual(results.failure_limit_count(), 30) # This value matches RunTests.NON_INTERACTIVE_FAILURE_LIMIT_COUNT
def test_archive_last_layout_test_results(self):
- tool = MockTool()
- reader = LayoutTestResultsReader(tool, "/var/logs")
- patch = tool.bugs.fetch_attachment(10001)
- tool.filesystem = MockFileSystem()
+ host = MockHost()
+ results_directory = "/mock-results"
+ reader = LayoutTestResultsReader(host, results_directory, "/var/logs")
+ patch = host.bugs.fetch_attachment(10001)
+ host.filesystem = MockFileSystem()
# Should fail because the results_directory does not exist.
expected_logs = "/mock-results does not exist, not archiving.\n"
archive = OutputCapture().assert_outputs(self, reader.archive, [patch], expected_logs=expected_logs)
- self.assertEqual(archive, None)
+ self.assertIsNone(archive)
- results_directory = "/mock-results"
- # Sanity check what we assume our mock results directory is.
- self.assertEqual(reader._results_directory(), results_directory)
- tool.filesystem.maybe_make_directory(results_directory)
- self.assertTrue(tool.filesystem.exists(results_directory))
+ host.filesystem.maybe_make_directory(results_directory)
+ self.assertTrue(host.filesystem.exists(results_directory))
+
+ self.assertIsNotNone(reader.archive(patch))
+ self.assertFalse(host.filesystem.exists(results_directory))
- self.assertNotEqual(reader.archive(patch), None)
- self.assertFalse(tool.filesystem.exists(results_directory))
+ def test_archive_last_layout_test_results_with_relative_path(self):
+ host = MockHost()
+ results_directory = "/mock-checkout/layout-test-results"
+
+ host.filesystem.maybe_make_directory(results_directory)
+ host.filesystem.maybe_make_directory('/var/logs')
+ self.assertTrue(host.filesystem.exists(results_directory))
+
+ host.filesystem.chdir('/var')
+ reader = LayoutTestResultsReader(host, results_directory, 'logs')
+ patch = host.bugs.fetch_attachment(10001)
+ # Should fail because the results_directory does not exist.
+ self.assertIsNotNone(reader.archive(patch))
+ self.assertEqual(host.workspace.source_path, results_directory)
+ self.assertEqual(host.workspace.zip_path, '/var/logs/50000-layout-test-results.zip')
diff --git a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py b/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py
index cde1c842e..b01c6c7e2 100644
--- a/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py
+++ b/Tools/Scripts/webkitpy/tool/bot/patchanalysistask.py
@@ -192,7 +192,7 @@ class PatchAnalysisTask(object):
return True
if self._test():
- # Only report flaky tests if we were successful at parsing results.html and archiving results.
+ # Only report flaky tests if we were successful at parsing results.json and archiving results.
if first_results and first_results_archive:
self._report_flaky_tests(first_results.failing_test_results(), first_results_archive)
return True
diff --git a/Tools/Scripts/webkitpy/tool/bot/queueengine.py b/Tools/Scripts/webkitpy/tool/bot/queueengine.py
index 6d5576e28..90e553f86 100644
--- a/Tools/Scripts/webkitpy/tool/bot/queueengine.py
+++ b/Tools/Scripts/webkitpy/tool/bot/queueengine.py
@@ -69,15 +69,14 @@ class QueueEngineDelegate:
class QueueEngine:
- def __init__(self, name, delegate, wakeup_event):
+ def __init__(self, name, delegate, wakeup_event, seconds_to_sleep=120):
self._name = name
self._delegate = delegate
self._wakeup_event = wakeup_event
self._output_tee = OutputTee()
+ self._seconds_to_sleep = seconds_to_sleep
log_date_format = "%Y-%m-%d %H:%M:%S"
- sleep_duration_text = "2 mins" # This could be generated from seconds_to_sleep
- seconds_to_sleep = 120
handled_error_code = 2
# Child processes exit with a special code to the parent queue process can detect the error was handled.
@@ -153,10 +152,14 @@ class QueueEngine:
return datetime.now()
def _sleep_message(self, message):
- wake_time = self._now() + timedelta(seconds=self.seconds_to_sleep)
- return "%s Sleeping until %s (%s)." % (message, wake_time.strftime(self.log_date_format), self.sleep_duration_text)
+ wake_time = self._now() + timedelta(seconds=self._seconds_to_sleep)
+ if self._seconds_to_sleep < 3 * 60:
+ sleep_duration_text = str(self._seconds_to_sleep) + ' seconds'
+ else:
+ sleep_duration_text = str(round(self._seconds_to_sleep / 60)) + ' minutes'
+ return "%s Sleeping until %s (%s)." % (message, wake_time.strftime(self.log_date_format), sleep_duration_text)
def _sleep(self, message):
_log.info(self._sleep_message(message))
- self._wakeup_event.wait(self.seconds_to_sleep)
+ self._wakeup_event.wait(self._seconds_to_sleep)
self._wakeup_event.clear()
diff --git a/Tools/Scripts/webkitpy/tool/bot/queueengine_unittest.py b/Tools/Scripts/webkitpy/tool/bot/queueengine_unittest.py
index 0ee8b5ad8..de9fa2398 100644
--- a/Tools/Scripts/webkitpy/tool/bot/queueengine_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/queueengine_unittest.py
@@ -31,7 +31,7 @@ import os
import shutil
import tempfile
import threading
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.executive import ScriptError
from webkitpy.common.system.outputcapture import OutputCapture
@@ -168,12 +168,12 @@ class QueueEngineTest(unittest.TestCase):
def test_now(self):
"""Make sure there are no typos in the QueueEngine.now() method."""
engine = QueueEngine("test", None, None)
- self.assertTrue(isinstance(engine._now(), datetime.datetime))
+ self.assertIsInstance(engine._now(), datetime.datetime)
def test_sleep_message(self):
engine = QueueEngine("test", None, None)
engine._now = lambda: datetime.datetime(2010, 1, 1)
- expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (2 mins)."
+ expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (120 seconds)."
self.assertEqual(engine._sleep_message("MESSAGE"), expected_sleep_message)
def setUp(self):
@@ -181,7 +181,3 @@ class QueueEngineTest(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.temp_dir)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/tool/bot/sheriff.py b/Tools/Scripts/webkitpy/tool/bot/sheriff.py
index b4e95aec0..9ef487537 100644
--- a/Tools/Scripts/webkitpy/tool/bot/sheriff.py
+++ b/Tools/Scripts/webkitpy/tool/bot/sheriff.py
@@ -36,6 +36,9 @@ class Sheriff(object):
self._tool = tool
self._sheriffbot = sheriffbot
+ def name(self):
+ return self._sheriffbot.name
+
def responsible_nicknames_from_commit_info(self, commit_info):
nestedList = [party.irc_nicknames for party in commit_info.responsible_parties() if party.irc_nicknames]
return reduce(lambda list, childList: list + childList, nestedList)
@@ -88,18 +91,6 @@ class Sheriff(object):
])
return urls.parse_bug_id(output)
- def post_chromium_deps_roll(self, revision, revision_name):
- args = [
- "post-chromium-deps-roll",
- "--force-clean",
- "--non-interactive",
- "--parent-command=sheriff-bot",
- ]
- # revision can be None, but revision_name is always something meaningful.
- args += [revision, revision_name]
- output = self._sheriffbot.run_webkit_patch(args)
- return urls.parse_bug_id(output)
-
def post_blame_comment_on_bug(self, commit_info, builders, tests):
if not commit_info.bug_id():
return
diff --git a/Tools/Scripts/webkitpy/tool/bot/sheriff_unittest.py b/Tools/Scripts/webkitpy/tool/bot/sheriff_unittest.py
index 02fc03608..cf989c9ce 100644
--- a/Tools/Scripts/webkitpy/tool/bot/sheriff_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/bot/sheriff_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.net.buildbot import Builder
from webkitpy.common.system.outputcapture import OutputCapture
diff --git a/Tools/Scripts/webkitpy/tool/commands/__init__.py b/Tools/Scripts/webkitpy/tool/commands/__init__.py
index 45711815e..a584ee037 100644
--- a/Tools/Scripts/webkitpy/tool/commands/__init__.py
+++ b/Tools/Scripts/webkitpy/tool/commands/__init__.py
@@ -5,11 +5,11 @@ from webkitpy.tool.commands.analyzechangelog import AnalyzeChangeLog
from webkitpy.tool.commands.applywatchlistlocal import ApplyWatchListLocal
from webkitpy.tool.commands.bugfortest import BugForTest
from webkitpy.tool.commands.bugsearch import BugSearch
-from webkitpy.tool.commands.chromechannels import ChromeChannels
from webkitpy.tool.commands.download import *
-from webkitpy.tool.commands.earlywarningsystem import *
+from webkitpy.tool.commands.earlywarningsystem import AbstractEarlyWarningSystem
from webkitpy.tool.commands.findusers import FindUsers
from webkitpy.tool.commands.gardenomatic import GardenOMatic
+from webkitpy.tool.commands.newcommitbot import NewCommitBot
from webkitpy.tool.commands.openbugs import OpenBugs
from webkitpy.tool.commands.perfalizer import Perfalizer
from webkitpy.tool.commands.prettydiff import PrettyDiff
@@ -17,7 +17,8 @@ from webkitpy.tool.commands.queries import *
from webkitpy.tool.commands.queues import *
from webkitpy.tool.commands.rebaseline import Rebaseline
from webkitpy.tool.commands.rebaselineserver import RebaselineServer
-from webkitpy.tool.commands.roll import *
from webkitpy.tool.commands.sheriffbot import *
from webkitpy.tool.commands.upload import *
from webkitpy.tool.commands.suggestnominations import *
+
+AbstractEarlyWarningSystem.load_ews_classes()
diff --git a/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py b/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py
index 0a7788c11..25a36ce0b 100644
--- a/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py
+++ b/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py
@@ -25,10 +25,10 @@
from optparse import make_option
import threading
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
-class AbstractLocalServerCommand(AbstractDeclarativeCommand):
+class AbstractLocalServerCommand(Command):
server = None
launch_path = "/"
@@ -37,7 +37,7 @@ class AbstractLocalServerCommand(AbstractDeclarativeCommand):
make_option("--httpd-port", action="store", type="int", default=8127, help="Port to use for the HTTP server"),
make_option("--no-show-results", action="store_false", default=True, dest="show_results", help="Don't launch a browser with the rebaseline server"),
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
def _prepare_config(self, options, args, tool):
return None
@@ -53,5 +53,5 @@ class AbstractLocalServerCommand(AbstractDeclarativeCommand):
# FIXME: This seems racy.
threading.Timer(0.1, lambda: self._tool.user.open_url(server_url)).start()
- httpd = self.server(httpd_port=options.httpd_port, config=config) # pylint: disable-msg=E1102
+ httpd = self.server(httpd_port=options.httpd_port, config=config) # pylint: disable=E1102
httpd.serve_forever()
diff --git a/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py b/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py
index 0593f2cfc..fcc76ca14 100644
--- a/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py
+++ b/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py
@@ -30,16 +30,16 @@ import logging
from webkitpy.common.system.executive import ScriptError
from webkitpy.tool.commands.stepsequence import StepSequence
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
_log = logging.getLogger(__name__)
-class AbstractSequencedCommand(AbstractDeclarativeCommand):
+class AbstractSequencedCommand(Command):
steps = None
def __init__(self):
self._sequence = StepSequence(self.steps)
- AbstractDeclarativeCommand.__init__(self, self._sequence.options())
+ Command.__init__(self, self._sequence.options())
def _prepare_state(self, options, args, tool):
return None
diff --git a/Tools/Scripts/webkitpy/tool/commands/adduserstogroups.py b/Tools/Scripts/webkitpy/tool/commands/adduserstogroups.py
index 22869584d..25e719f61 100644
--- a/Tools/Scripts/webkitpy/tool/commands/adduserstogroups.py
+++ b/Tools/Scripts/webkitpy/tool/commands/adduserstogroups.py
@@ -26,10 +26,10 @@
# (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.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
-class AddUsersToGroups(AbstractDeclarativeCommand):
+class AddUsersToGroups(Command):
name = "add-users-to-groups"
help_text = "Add users matching subtring to specified groups"
diff --git a/Tools/Scripts/webkitpy/tool/commands/analyzechangelog.py b/Tools/Scripts/webkitpy/tool/commands/analyzechangelog.py
index b88b61f55..1a1e810de 100644
--- a/Tools/Scripts/webkitpy/tool/commands/analyzechangelog.py
+++ b/Tools/Scripts/webkitpy/tool/commands/analyzechangelog.py
@@ -35,11 +35,11 @@ from webkitpy.common.checkout.changelog import ChangeLog
from webkitpy.common.config.contributionareas import ContributionAreas
from webkitpy.common.system.filesystem import FileSystem
from webkitpy.common.system.executive import Executive
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
from webkitpy.tool import steps
-class AnalyzeChangeLog(AbstractDeclarativeCommand):
+class AnalyzeChangeLog(Command):
name = "analyze-changelog"
help_text = "Experimental command for analyzing change logs."
long_help = "This command parses changelogs in a specified directory and summarizes the result as JSON files."
@@ -48,7 +48,7 @@ class AnalyzeChangeLog(AbstractDeclarativeCommand):
options = [
steps.Options.changelog_count,
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
@staticmethod
def _enumerate_changelogs(filesystem, dirname, changelog_count):
@@ -180,6 +180,7 @@ class ChangeLogAnalyzer(object):
def _analyze_entries(self, entries, changelog_path):
dirname = self._filesystem.dirname(changelog_path)
+ i = 0
for i, entry in enumerate(entries):
self._print_status('(%s) entries' % i)
assert(entry.authors())
@@ -201,6 +202,5 @@ class ChangeLogAnalyzer(object):
self._summary['reviewed' if reviewers_for_entry else 'unreviewed'] += 1
- i += 1
self._print_status('(%s) entries' % i)
return i
diff --git a/Tools/Scripts/webkitpy/tool/commands/analyzechangelog_unittest.py b/Tools/Scripts/webkitpy/tool/commands/analyzechangelog_unittest.py
index 661d2d85f..9c13740a2 100644
--- a/Tools/Scripts/webkitpy/tool/commands/analyzechangelog_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/analyzechangelog_unittest.py
@@ -36,7 +36,7 @@ from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.commands.analyzechangelog import AnalyzeChangeLog
from webkitpy.tool.commands.analyzechangelog import ChangeLogAnalyzer
from webkitpy.tool.commands.commandtest import CommandsTest
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
class AnalyzeChangeLogTest(CommandsTest):
diff --git a/Tools/Scripts/webkitpy/tool/commands/bugfortest.py b/Tools/Scripts/webkitpy/tool/commands/bugfortest.py
index 36aa6b5f1..f6f84115e 100644
--- a/Tools/Scripts/webkitpy/tool/commands/bugfortest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/bugfortest.py
@@ -26,14 +26,14 @@
# (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.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
from webkitpy.tool.bot.flakytestreporter import FlakyTestReporter
# This is mostly a command for testing FlakyTestReporter, however
# it could be easily expanded to auto-create bugs, etc. if another
# command outside of webkitpy wanted to use it.
-class BugForTest(AbstractDeclarativeCommand):
+class BugForTest(Command):
name = "bug-for-test"
help_text = "Finds the bugzilla bug for a given test"
diff --git a/Tools/Scripts/webkitpy/tool/commands/bugsearch.py b/Tools/Scripts/webkitpy/tool/commands/bugsearch.py
index a1d74c548..1f3af7a70 100644
--- a/Tools/Scripts/webkitpy/tool/commands/bugsearch.py
+++ b/Tools/Scripts/webkitpy/tool/commands/bugsearch.py
@@ -26,10 +26,10 @@
# (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.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
-class BugSearch(AbstractDeclarativeCommand):
+class BugSearch(Command):
name = "bug-search"
help_text = "List bugs matching a query"
argument_names = "QUERY"
diff --git a/Tools/Scripts/webkitpy/tool/commands/chromechannels.py b/Tools/Scripts/webkitpy/tool/commands/chromechannels.py
deleted file mode 100644
index da093b48c..000000000
--- a/Tools/Scripts/webkitpy/tool/commands/chromechannels.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (c) 2012 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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.
-
-from optparse import make_option
-
-from webkitpy.common.net.omahaproxy import OmahaProxy
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
-
-import re
-
-
-class ChromeChannels(AbstractDeclarativeCommand):
- name = "chrome-channels"
- help_text = "List which chrome channels include the patches in bugs returned by QUERY."
- argument_names = "QUERY"
- long_help = """Retrieves the current list of Chrome releases from omahaproxy.appspot.com,
-and then runs the bugzilla quicksearch QUERY on bugs.bugzilla.org. For each bug
-returned by query, a single svn commit is deduced, and a short summary is
-printed of each bug listing which Chrome channels contain each bugs associated
-commit.
-
-The QUERY can be as simple as a bug number, or a comma delimited list of bug
-numbers. See https://bugzilla.mozilla.org/page.cgi?id=quicksearch.html for full
-documentation on the query format."""
-
- chrome_channels = OmahaProxy.chrome_channels
- commited_pattern = "Committed r([0-9]+): <http://trac.webkit.org/changeset/\\1>"
- rollout_pattern = "Rolled out in http://trac.webkit.org/changeset/[0-9]+"
-
- def __init__(self):
- AbstractDeclarativeCommand.__init__(self)
- self._re_committed = re.compile(self.commited_pattern)
- self._re_rollout = re.compile(self.rollout_pattern)
- self._omahaproxy = OmahaProxy()
-
- def _channels_for_bug(self, revisions, bug):
- comments = bug.comments()
- commit = None
-
- # Scan the comments, looking for a sane list of commits and rollbacks.
- for comment in comments:
- commit_match = self._re_committed.search(comment['text'])
- if commit_match:
- if commit:
- return "%5s %s\n... has too confusing a commit history to parse, skipping\n" % (bug.id(), bug.title())
- commit = int(commit_match.group(1))
- if self._re_rollout.search(comment['text']):
- commit = None
- if not commit:
- return "%5s %s\n... does not appear to have an associated commit.\n" % (bug.id(), bug.title())
-
- # We now know that we have a commit, so gather up the list of platforms
- # by channel, then print.
- by_channel = {}
- for revision in revisions:
- channel = revision['channel']
- if revision['commit'] < commit:
- continue
- if not channel in by_channel:
- by_channel[revision['channel']] = " %6s:" % channel
- by_channel[channel] += " %s," % revision['platform']
- if not by_channel:
- return "%5s %s (r%d)\n... not yet released in any Chrome channels.\n" % (bug.id(), bug.title(), commit)
- retval = "%5s %s (r%d)\n" % (bug.id(), bug.title(), commit)
- for channel in self.chrome_channels:
- if channel in by_channel:
- retval += by_channel[channel][:-1]
- retval += "\n"
- return retval
-
- def execute(self, options, args, tool):
- search_string = args[0]
- revisions = self._omahaproxy.get_revisions()
- bugs = tool.bugs.queries.fetch_bugs_matching_quicksearch(search_string)
- if not bugs:
- print "No bugs found matching '%s'" % search_string
- return
- for bug in bugs:
- print self._channels_for_bug(revisions, bug),
diff --git a/Tools/Scripts/webkitpy/tool/commands/chromechannels_unittest.py b/Tools/Scripts/webkitpy/tool/commands/chromechannels_unittest.py
deleted file mode 100644
index 037aebbfe..000000000
--- a/Tools/Scripts/webkitpy/tool/commands/chromechannels_unittest.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright (c) 2012 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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.
-
-from webkitpy.tool.commands.chromechannels import ChromeChannels
-from webkitpy.tool.commands.commandtest import CommandsTest
-from webkitpy.tool.mocktool import MockTool
-from webkitpy.common.net.omahaproxy import OmahaProxy
-
-
-class MockOmahaProxy(OmahaProxy):
- revisions = [{"commit": 20, "channel": "canary", "platform": "Mac", "date": "07/04/76"},
- {"commit": 20, "channel": "canary", "platform": "Windows", "date": "07/04/76"},
- {"commit": 25, "channel": "dev", "platform": "Mac", "date": "07/01/76"},
- {"commit": 30, "channel": "dev", "platform": "Windows", "date": "03/29/82"},
- {"commit": 30, "channel": "dev", "platform": "Linux", "date": "03/29/82"},
- {"commit": 15, "channel": "beta", "platform": "Windows", "date": "07/04/67"},
- {"commit": 15, "channel": "beta", "platform": "Linux", "date": "07/04/67"},
- {"commit": 10, "channel": "stable", "platform": "Windows", "date": "07/01/67"},
- {"commit": 20, "channel": "stable", "platform": "Linux", "date": "09/16/10"},
- ]
-
- def get_revisions(self):
- return self.revisions
-
-
-class TestableChromeChannels(ChromeChannels):
- def __init__(self):
- ChromeChannels.__init__(self)
- self._omahaproxy = MockOmahaProxy()
-
-
-class ChromeChannelsTest(CommandsTest):
-
- single_bug_expectations = {
- 50001: """50001 Bug with a patch needing review. (r35)
-... not yet released in any Chrome channels.
-""",
- 50002: """50002 The third bug
-... has too confusing a commit history to parse, skipping
-""",
- 50003: """50003 The fourth bug
-... does not appear to have an associated commit.
-""",
- 50004: """50004 The fifth bug (r15)
- canary: Mac, Windows
- dev: Mac, Windows, Linux
- beta: Windows, Linux
- stable: Linux
-"""}
-
- def test_single_bug(self):
- testable_chrome_channels = TestableChromeChannels()
- tool = MockTool()
- testable_chrome_channels.bind_to_tool(tool)
- revisions = testable_chrome_channels._omahaproxy.get_revisions()
- for bug_id, expectation in self.single_bug_expectations.items():
- self.assertEqual(testable_chrome_channels._channels_for_bug(revisions, testable_chrome_channels._tool.bugs.fetch_bug(bug_id)),
- expectation)
-
- def test_with_query(self):
- expected_stdout = \
-"""50001 Bug with a patch needing review. (r35)
-... not yet released in any Chrome channels.
-50002 The third bug
-... has too confusing a commit history to parse, skipping
-50003 The fourth bug
-... does not appear to have an associated commit.
-50004 The fifth bug (r15)
- canary: Mac, Windows
- dev: Mac, Windows, Linux
- beta: Windows, Linux
- stable: Linux
-"""
- self.assert_execute_outputs(TestableChromeChannels(), ["foo"], expected_stdout=expected_stdout)
diff --git a/Tools/Scripts/webkitpy/tool/commands/commandtest.py b/Tools/Scripts/webkitpy/tool/commands/commandtest.py
index 65f45b58f..655c33fda 100644
--- a/Tools/Scripts/webkitpy/tool/commands/commandtest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/commandtest.py
@@ -42,6 +42,7 @@ class CommandsTest(TestCase):
options.obsolete_patches = True
options.open_bug = True
options.port = 'MOCK port'
+ options.update_changelogs = False
options.quiet = True
options.reviewer = 'MOCK reviewer'
command.bind_to_tool(tool)
diff --git a/Tools/Scripts/webkitpy/tool/commands/download.py b/Tools/Scripts/webkitpy/tool/commands/download.py
index bdd780d2c..85f576928 100644
--- a/Tools/Scripts/webkitpy/tool/commands/download.py
+++ b/Tools/Scripts/webkitpy/tool/commands/download.py
@@ -38,7 +38,7 @@ from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCom
from webkitpy.tool.commands.stepsequence import StepSequence
from webkitpy.tool.comments import bug_comment_from_commit_text
from webkitpy.tool.grammar import pluralize
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
_log = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ class Clean(AbstractSequencedCommand):
name = "clean"
help_text = "Clean the working copy"
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
]
def _prepare_state(self, options, args, tool):
@@ -58,7 +58,7 @@ class Update(AbstractSequencedCommand):
name = "update"
help_text = "Update working copy (used internally)"
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
]
@@ -67,7 +67,7 @@ class Build(AbstractSequencedCommand):
name = "build"
help_text = "Update working copy and build"
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.Build,
]
@@ -80,7 +80,7 @@ class BuildAndTest(AbstractSequencedCommand):
name = "build-and-test"
help_text = "Update working copy, build, and run the tests"
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.Build,
steps.RunTests,
@@ -114,8 +114,9 @@ If a bug id is provided, or one can be found in the ChangeLog land will update t
}
-class LandCowboy(AbstractSequencedCommand):
- name = "land-cowboy"
+class LandCowhand(AbstractSequencedCommand):
+ # Gender-blind term for cowboy, see: http://en.wiktionary.org/wiki/cowhand
+ name = "land-cowhand"
help_text = "Prepares a ChangeLog and lands the current working directory diff."
steps = [
steps.PrepareChangeLog,
@@ -132,9 +133,12 @@ class LandCowboy(AbstractSequencedCommand):
options.check_style_filter = "-changelog"
-class LandCowhand(LandCowboy):
- # Gender-blind term for cowboy, see: http://en.wiktionary.org/wiki/cowhand
- name = "land-cowhand"
+class LandCowboy(LandCowhand):
+ name = "land-cowboy"
+
+ def _prepare_state(self, options, args, tool):
+ _log.warning("land-cowboy is deprecated, use land-cowhand instead.")
+ LandCowhand._prepare_state(self, options, args, tool)
class CheckStyleLocal(AbstractSequencedCommand):
@@ -145,11 +149,11 @@ class CheckStyleLocal(AbstractSequencedCommand):
]
-class AbstractPatchProcessingCommand(AbstractDeclarativeCommand):
+class AbstractPatchProcessingCommand(Command):
# Subclasses must implement the methods below. We don't declare them here
# because we want to be able to implement them with mix-ins.
#
- # pylint: disable-msg=E1101
+ # pylint: disable=E1101
# def _fetch_list_of_patches_to_process(self, options, args, tool):
# def _prepare_to_process(self, options, args, tool):
# def _process_patch(self, options, args, tool):
@@ -185,12 +189,22 @@ class AbstractPatchSequencingCommand(AbstractPatchProcessingCommand):
AbstractPatchProcessingCommand.__init__(self, options)
def _prepare_to_process(self, options, args, tool):
- self._prepare_sequence.run_and_handle_errors(tool, options)
+ try:
+ self.state = self._prepare_state(options, args, tool)
+ except ScriptError, e:
+ _log.error(e.message_with_output())
+ self._exit(e.exit_code or 2)
+ self._prepare_sequence.run_and_handle_errors(tool, options, self.state)
def _process_patch(self, patch, options, args, tool):
- state = { "patch" : patch }
+ state = {}
+ state.update(self.state or {})
+ state["patch"] = patch
self._main_sequence.run_and_handle_errors(tool, options, state)
+ def _prepare_state(self, options, args, tool):
+ return None
+
class ProcessAttachmentsMixin(object):
def _fetch_list_of_patches_to_process(self, options, args, tool):
@@ -235,7 +249,7 @@ class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
help_text = "Run check-webkit-style on the specified attachments"
argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
main_steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.ApplyPatch,
steps.CheckStyle,
@@ -247,7 +261,7 @@ class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
help_text = "Apply and build patches from bugzilla"
argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
main_steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.ApplyPatch,
steps.Build,
@@ -259,7 +273,7 @@ class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsM
help_text = "Apply, build, and test patches from bugzilla"
argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
main_steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.ApplyPatch,
steps.Build,
@@ -270,7 +284,7 @@ class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsM
class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand):
prepare_steps = [
steps.EnsureLocalCommitIfNeeded,
- steps.CleanWorkingDirectoryWithLocalCommits,
+ steps.CleanWorkingDirectory,
steps.Update,
]
main_steps = [
@@ -299,7 +313,7 @@ class ApplyWatchList(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
help_text = "Applies the watchlist to the specified attachments"
argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]"
main_steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.ApplyPatch,
steps.ApplyWatchList,
@@ -310,7 +324,7 @@ Downloads the attachment, applies it locally, runs the watchlist against it, and
class AbstractPatchLandingCommand(AbstractPatchSequencingCommand):
main_steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.ApplyPatch,
steps.ValidateChangeLogs,
@@ -413,7 +427,7 @@ Applies the inverse diff for the provided revision(s).
Creates an appropriate rollout ChangeLog, including a trac link and bug link.
"""
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.RevertRevision,
steps.PrepareChangeLogForRevert,
@@ -424,7 +438,7 @@ class CreateRollout(AbstractRolloutPrepCommand):
name = "create-rollout"
help_text = "Creates a bug to track the broken SVN revision(s) and uploads a rollout patch."
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.RevertRevision,
steps.CreateBug,
@@ -470,7 +484,7 @@ Opens the generated ChangeLogs in $EDITOR.
Shows the prepared diff for confirmation.
Commits the revert and updates the bug (including re-opening the bug if necessary)."""
steps = [
- steps.CleanWorkingDirectory,
+ steps.DiscardLocalChanges,
steps.Update,
steps.RevertRevision,
steps.PrepareChangeLogForRevert,
diff --git a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
index 14bf2ce5e..d35706f7e 100644
--- a/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/download_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.thirdparty.mock import Mock
@@ -99,6 +99,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
"""
@@ -140,6 +141,7 @@ Message2."
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -153,7 +155,7 @@ Updating bug 50000
self.assertEqual(mock_tool.scm().create_patch.call_count, 0)
self.assertEqual(mock_tool.checkout().modified_changelogs.call_count, 1)
- def test_land_cowboy(self):
+ def test_land_cowhand(self):
expected_logs = """MOCK run_and_throw_if_fail: ['mock-prepare-ChangeLog', '--email=MOCK email', '--merge-base=None', 'MockFile1'], cwd=/mock-checkout
MOCK run_and_throw_if_fail: ['mock-check-webkit-style', '--git-commit', 'MOCK git commit', '--diff-files', 'MockFile1', '--filter', '-changelog'], cwd=/mock-checkout
MOCK run_command: ['ruby', '-I', '/mock-checkout/Websites/bugs.webkit.org/PrettyPatch', '/mock-checkout/Websites/bugs.webkit.org/PrettyPatch/prettify.rb'], cwd=None, input=Patch1
@@ -167,6 +169,8 @@ Running Perl unit tests
MOCK run_and_throw_if_fail: ['mock-test-webkitperl'], cwd=/mock-checkout
Running JavaScriptCore tests
MOCK run_and_throw_if_fail: ['mock-run-javacriptcore-tests'], cwd=/mock-checkout
+Running bindings generation tests
+MOCK run_and_throw_if_fail: ['mock-run-bindings-tests'], cwd=/mock-checkout
Running WebKit unit tests
MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests'], cwd=/mock-checkout
Running run-webkit-tests
@@ -176,6 +180,9 @@ Committed r49824: <http://trac.webkit.org/changeset/49824>
No bug id provided.
"""
mock_tool = MockTool(log_executive=True)
+ self.assert_execute_outputs(LandCowhand(), [50000], options=self._default_options(), expected_logs=expected_logs, tool=mock_tool)
+
+ expected_logs = "land-cowboy is deprecated, use land-cowhand instead.\n" + expected_logs
self.assert_execute_outputs(LandCowboy(), [50000], options=self._default_options(), expected_logs=expected_logs, tool=mock_tool)
def test_land_red_builders(self):
@@ -183,6 +190,7 @@ No bug id provided.
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -214,6 +222,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -231,6 +240,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -241,6 +251,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -258,6 +269,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
@@ -268,6 +280,7 @@ Building WebKit
Running Python unit tests
Running Perl unit tests
Running JavaScriptCore tests
+Running bindings generation tests
Running WebKit unit tests
Running run-webkit-tests
Committed r49824: <http://trac.webkit.org/changeset/49824>
diff --git a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py
index 98a9a36ed..b5e285c64 100644
--- a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py
+++ b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem.py
@@ -26,11 +26,13 @@
# (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 optparse import make_option
from webkitpy.common.config.committers import CommitterList
from webkitpy.common.config.ports import DeprecatedPort
+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.expectedfailures import ExpectedFailures
@@ -45,22 +47,16 @@ _log = logging.getLogger(__name__)
class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDelegate):
_build_style = "release"
# FIXME: Switch _default_run_tests from opt-in to opt-out once more bots are ready to run tests.
- _default_run_tests = False
-
- # Subclasses must override.
- port_name = None
+ run_tests = False
def __init__(self):
- options = [make_option("--run-tests", action="store_true", dest="run_tests", default=self._default_run_tests, help="Run the Layout tests for each patch")]
+ options = [make_option("--run-tests", action="store_true", dest="run_tests", default=self.run_tests, help="Run the Layout tests for each patch")]
AbstractReviewQueue.__init__(self, options=options)
- self.port = DeprecatedPort.port(self.port_name)
def begin_work_queue(self):
- # FIXME: This violates abstraction
- self._tool._deprecated_port = self.port
AbstractReviewQueue.begin_work_queue(self)
self._expected_failures = ExpectedFailures()
- self._layout_test_results_reader = LayoutTestResultsReader(self._tool, self._log_directory())
+ self._layout_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)
@@ -72,11 +68,13 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
def _post_reject_message_on_bug(self, tool, patch, status_id, extra_message_text=None):
results_link = tool.status_server.results_url_for_status(status_id)
message = "Attachment %s did not pass %s (%s):\nOutput: %s" % (patch.id(), self.name, self.port_name, results_link)
+ if extra_message_text:
+ message += "\n\n%s" % extra_message_text
# FIXME: We might want to add some text about rejecting from the commit-queue in
# the case where patch.commit_queue() isn't already set to '-'.
if self.watchers:
tool.bugs.add_cc_to_bug(patch.bug_id(), self.watchers)
- tool.bugs.set_flag_on_attachment(patch.id(), "commit-queue", "-", message, extra_message_text)
+ tool.bugs.set_flag_on_attachment(patch.id(), "commit-queue", "-", message)
def review_patch(self, patch):
task = EarlyWarningSystemTask(self, patch, self._options.run_tests)
@@ -105,7 +103,7 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
return self.name
def run_command(self, command):
- self.run_webkit_patch(command + [self.port.flag()])
+ self.run_webkit_patch(command + [self._deprecated_port.flag()])
def command_passed(self, message, patch):
pass
@@ -139,78 +137,21 @@ class AbstractEarlyWarningSystem(AbstractReviewQueue, EarlyWarningSystemTaskDele
# FIXME: Why does this not exit(1) like the superclass does?
_log.error(script_error.message_with_output())
+ @classmethod
+ def load_ews_classes(cls):
+ filesystem = FileSystem()
+ json_path = filesystem.join(filesystem.dirname(filesystem.path_to_module('webkitpy.common.config')), 'ews.json')
+ try:
+ ewses = json.loads(filesystem.read_text_file(json_path))
+ except ValueError:
+ return None
-class GtkEWS(AbstractEarlyWarningSystem):
- name = "gtk-ews"
- port_name = "gtk"
- watchers = AbstractEarlyWarningSystem.watchers + [
- "xan.lopez@gmail.com",
- ]
-
-
-class EflEWS(AbstractEarlyWarningSystem):
- name = "efl-ews"
- port_name = "efl"
- watchers = AbstractEarlyWarningSystem.watchers + [
- "leandro@profusion.mobi",
- "antognolli@profusion.mobi",
- "lucas.demarchi@profusion.mobi",
- "gyuyoung.kim@samsung.com",
- ]
-
-
-class QtEWS(AbstractEarlyWarningSystem):
- name = "qt-ews"
- port_name = "qt"
- watchers = AbstractEarlyWarningSystem.watchers + [
- "webkit-ews@sed.inf.u-szeged.hu",
- ]
-
-
-class QtWK2EWS(AbstractEarlyWarningSystem):
- name = "qt-wk2-ews"
- port_name = "qt"
- watchers = AbstractEarlyWarningSystem.watchers + [
- "webkit-ews@sed.inf.u-szeged.hu",
- ]
-
-
-class WinEWS(AbstractEarlyWarningSystem):
- name = "win-ews"
- port_name = "win"
- # Use debug, the Apple Win port fails to link Release on 32-bit Windows.
- # https://bugs.webkit.org/show_bug.cgi?id=39197
- _build_style = "debug"
-
-
-class AbstractChromiumEWS(AbstractEarlyWarningSystem):
- port_name = "chromium"
- watchers = AbstractEarlyWarningSystem.watchers + [
- "dglazkov@chromium.org",
- ]
-
-
-class ChromiumLinuxEWS(AbstractChromiumEWS):
- # FIXME: We should rename this command to cr-linux-ews, but that requires
- # a database migration. :(
- name = "chromium-ews"
- port_name = "chromium-xvfb"
- _default_run_tests = True
-
-
-class ChromiumWindowsEWS(AbstractChromiumEWS):
- name = "cr-win-ews"
-
-
-class ChromiumAndroidEWS(AbstractChromiumEWS):
- name = "cr-android-ews"
- port_name = "chromium-android"
- watchers = AbstractChromiumEWS.watchers + [
- "peter+ews@chromium.org",
- ]
-
-
-class MacEWS(AbstractEarlyWarningSystem):
- name = "mac-ews"
- port_name = "mac"
- _default_run_tests = True
+ classes = []
+ for name, config in ewses.iteritems():
+ classes.append(type(str(name.replace(' ', '')), (AbstractEarlyWarningSystem,), {
+ 'name': config['port'] + '-ews',
+ 'port_name': config['port'],
+ 'watchers': config.get('watchers', []),
+ 'run_tests': config.get('runTests', cls.run_tests),
+ }))
+ return classes
diff --git a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py
index b33129a20..78dae3ba9 100644
--- a/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py
@@ -47,44 +47,45 @@ class AbstractEarlyWarningSystemTest(QueuesTest):
ews._expected_failures.unexpected_failures_observed = lambda results: set(["foo.html", "bar.html"])
task = Mock()
patch = ews._tool.bugs.fetch_attachment(10000)
- self.assertEqual(ews._failing_tests_message(task, patch), "New failing tests:\nbar.html\nfoo.html")
+ self.assertMultiLineEqual(ews._failing_tests_message(task, patch), "New failing tests:\nbar.html\nfoo.html")
-class EarlyWarningSytemTest(QueuesTest):
+class EarlyWarningSystemTest(QueuesTest):
def _default_expected_logs(self, ews):
- string_replacemnts = {
+ string_replacements = {
"name": ews.name,
"port": ews.port_name,
}
+ if ews.run_tests:
+ run_tests_line = "Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --port=%(port)s\n" % string_replacements
+ else:
+ run_tests_line = ""
+ string_replacements['run_tests_line'] = run_tests_line
+
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs(ews.name),
- "process_work_item": "MOCK: update_status: %(name)s Pass\nMOCK: release_work_item: %(name)s 10000\n" % string_replacemnts,
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean --port=%(port)s
+Running: webkit-patch --status-host=example.com update --port=%(port)s
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000 --port=%(port)s
+Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=release --port=%(port)s
+%(run_tests_line)sMOCK: update_status: %(name)s Pass
+MOCK: release_work_item: %(name)s 10000
+""" % string_replacements,
"handle_unexpected_error": "Mock error message\n",
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
}
return expected_logs
- def _test_builder_ews(self, ews):
+ def _test_ews(self, ews):
ews.bind_to_tool(MockTool())
options = Mock()
options.port = None
- options.run_tests = ews._default_run_tests
+ options.run_tests = ews.run_tests
self.assert_queue_outputs(ews, expected_logs=self._default_expected_logs(ews), options=options)
- def _test_testing_ews(self, ews):
- ews.test_results = lambda: None
- ews.bind_to_tool(MockTool())
- expected_logs = self._default_expected_logs(ews)
- self.assert_queue_outputs(ews, expected_logs=expected_logs)
-
- def test_builder_ewses(self):
- self._test_builder_ews(MacEWS())
- self._test_builder_ews(ChromiumWindowsEWS())
- self._test_builder_ews(ChromiumAndroidEWS())
- self._test_builder_ews(QtEWS())
- self._test_builder_ews(QtWK2EWS())
- self._test_builder_ews(GtkEWS())
- self._test_builder_ews(EflEWS())
-
- def test_testing_ewses(self):
- self._test_testing_ews(ChromiumLinuxEWS())
+ def test_ewses(self):
+ classes = AbstractEarlyWarningSystem.load_ews_classes()
+ self.assertTrue(classes)
+ self.maxDiff = None
+ for ews_class in classes:
+ self._test_ews(ews_class())
diff --git a/Tools/Scripts/webkitpy/tool/commands/findusers.py b/Tools/Scripts/webkitpy/tool/commands/findusers.py
index 4363c8cf2..ae4702373 100644
--- a/Tools/Scripts/webkitpy/tool/commands/findusers.py
+++ b/Tools/Scripts/webkitpy/tool/commands/findusers.py
@@ -26,10 +26,10 @@
# (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.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
-class FindUsers(AbstractDeclarativeCommand):
+class FindUsers(Command):
name = "find-users"
help_text = "Find users matching substring"
diff --git a/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py b/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py
index c87c1a265..e9762858d 100644
--- a/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py
+++ b/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py
@@ -22,7 +22,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.layout_tests.port import builders
+from webkitpy.port import builders
from webkitpy.tool.commands.rebaseline import AbstractRebaseliningCommand
from webkitpy.tool.servers.gardeningserver import GardeningHTTPServer
diff --git a/Tools/Scripts/webkitpy/tool/commands/newcommitbot.py b/Tools/Scripts/webkitpy/tool/commands/newcommitbot.py
new file mode 100644
index 000000000..958576158
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/commands/newcommitbot.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2012 Google Inc. All rights reserved.
+# Copyright (c) 2013 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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.config.committers import CommitterList
+from webkitpy.common.system.executive import ScriptError
+from webkitpy.tool.bot.irc_command import IRCCommand
+from webkitpy.tool.bot.irc_command import Help
+from webkitpy.tool.bot.irc_command import Hi
+from webkitpy.tool.bot.irc_command import PingPong
+from webkitpy.tool.bot.irc_command import Restart
+from webkitpy.tool.bot.irc_command import YouThere
+from webkitpy.tool.bot.ircbot import IRCBot
+from webkitpy.tool.commands.queues import AbstractQueue
+from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler
+
+_log = logging.getLogger(__name__)
+
+
+class Agent(object):
+ def __init__(self, tool, newcommitbot):
+ self._tool = tool
+ self._newcommitbot = newcommitbot
+
+ def name(self):
+ return 'WKR'
+
+
+class NewCommitBot(AbstractQueue, StepSequenceErrorHandler):
+ name = "WKR"
+ watchers = AbstractQueue.watchers + ["rniwa@webkit.org"]
+
+ _commands = {
+ "hi": Hi,
+ "ping": PingPong,
+ "restart": Restart,
+ "yt?": YouThere,
+ }
+
+ _maximum_number_of_revisions_to_avoid_spamming_irc = 10
+
+ # AbstractQueue methods
+
+ def begin_work_queue(self):
+ AbstractQueue.begin_work_queue(self)
+ self._last_svn_revision = int(self._tool.scm().head_svn_revision())
+ self._irc_bot = IRCBot(self.name, self._tool, Agent(self._tool, self), self._commands)
+ self._tool.ensure_irc_connected(self._irc_bot.irc_delegate())
+
+ def work_item_log_path(self, failure_map):
+ return None
+
+ def next_work_item(self):
+ self._irc_bot.process_pending_messages()
+
+ _log.info('Last SVN revision: %d' % self._last_svn_revision)
+
+ count = 0
+ while count < self._maximum_number_of_revisions_to_avoid_spamming_irc:
+ new_revision = self._last_svn_revision + 1
+ try:
+ commit_log = self._tool.executive.run_command(['svn', 'log', 'https://svn.webkit.org/repository/webkit/trunk', '--non-interactive', '--revision',
+ self._tool.scm().strip_r_from_svn_revision(new_revision)])
+ except ScriptError:
+ break
+
+ self._last_svn_revision = new_revision
+ if self._is_empty_log(commit_log):
+ continue
+
+ count += 1
+ _log.info('Found revision %d' % new_revision)
+ self._tool.irc().post(self._summarize_commit_log(commit_log).encode('utf-8'))
+
+ def _is_empty_log(self, commit_log):
+ return re.match(r'^\-+$', commit_log)
+
+ def process_work_item(self, failure_map):
+ return True
+
+ _patch_by_regex = re.compile(r'^Patch\s+by\s+(?P<author>.+?)\s+on(\s+\d{4}-\d{2}-\d{2})?\n?', re.MULTILINE | re.IGNORECASE)
+ _rollout_regex = re.compile(r'(rolling out|reverting) (?P<revisions>r?\d+((,\s*|,?\s*and\s+)?r?\d+)*)\.?\s*', re.MULTILINE | re.IGNORECASE)
+ _requested_by_regex = re.compile(r'^\"?(?P<reason>.+?)\"? \(Requested\s+by\s+(?P<author>.+?)\s+on\s+#webkit\)\.', re.MULTILINE | re.IGNORECASE)
+ _bugzilla_url_regex = re.compile(r'http(s?)://bugs\.webkit\.org/show_bug\.cgi\?id=(?P<id>\d+)', re.MULTILINE)
+ _trac_url_regex = re.compile(r'http(s?)://trac.webkit.org/changeset/(?P<revision>\d+)', re.MULTILINE)
+
+ @classmethod
+ def _summarize_commit_log(self, commit_log, committer_list=CommitterList()):
+ patch_by = self._patch_by_regex.search(commit_log)
+ commit_log = self._patch_by_regex.sub('', commit_log, count=1)
+
+ rollout = self._rollout_regex.search(commit_log)
+ commit_log = self._rollout_regex.sub('', commit_log, count=1)
+
+ requested_by = self._requested_by_regex.search(commit_log)
+
+ commit_log = self._bugzilla_url_regex.sub(r'https://webkit.org/b/\g<id>', commit_log)
+ commit_log = self._trac_url_regex.sub(r'https://trac.webkit.org/r\g<revision>', commit_log)
+
+ for contributor in committer_list.contributors():
+ if not contributor.irc_nicknames:
+ continue
+ name_with_nick = "%s (%s)" % (contributor.full_name, contributor.irc_nicknames[0])
+ if contributor.full_name in commit_log:
+ commit_log = commit_log.replace(contributor.full_name, name_with_nick)
+ for email in contributor.emails:
+ commit_log = commit_log.replace(' <' + email + '>', '')
+ else:
+ for email in contributor.emails:
+ commit_log = commit_log.replace(email, name_with_nick)
+
+ lines = commit_log.split('\n')[1:-2] # Ignore lines with ----------.
+
+ firstline = re.match(r'^(?P<revision>r\d+) \| (?P<email>[^\|]+) \| (?P<timestamp>[^|]+) \| [^\n]+', lines[0])
+ assert firstline
+ author = firstline.group('email')
+ if patch_by:
+ author = patch_by.group('author')
+
+ linkified_revision = 'https://trac.webkit.org/%s' % firstline.group('revision')
+ lines[0] = '%s by %s' % (linkified_revision, author)
+
+ if rollout:
+ if requested_by:
+ author = requested_by.group('author')
+ contributor = committer_list.contributor_by_irc_nickname(author)
+ if contributor:
+ author = "%s (%s)" % (contributor.full_name, contributor.irc_nicknames[0])
+ return '%s rolled out %s in %s : %s' % (author, rollout.group('revisions'),
+ linkified_revision, requested_by.group('reason'))
+ lines[0] = '%s rolled out %s in %s' % (author, rollout.group('revisions'), linkified_revision)
+
+ return ' '.join(filter(lambda line: len(line), lines)[0:4])
+
+ def handle_unexpected_error(self, failure_map, message):
+ _log.error(message)
+
+ # StepSequenceErrorHandler methods
+
+ @classmethod
+ def handle_script_error(cls, tool, state, script_error):
+ # Ideally we would post some information to IRC about what went wrong
+ # here, but we don't have the IRC password in the child process.
+ pass
diff --git a/Tools/Scripts/webkitpy/tool/commands/newcommitbot_unittest.py b/Tools/Scripts/webkitpy/tool/commands/newcommitbot_unittest.py
new file mode 100644
index 000000000..05bf45664
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/commands/newcommitbot_unittest.py
@@ -0,0 +1,129 @@
+# Copyright (C) 2013 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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 unittest2 as unittest
+
+from webkitpy.tool.commands.newcommitbot import NewCommitBot
+
+
+class NewCommitBotTest(unittest.TestCase):
+ def test_summarize_commit_log_basic(self):
+ self.assertEqual(NewCommitBot._summarize_commit_log("""------------------------------------------------------------------------
+r143106 | jochen@chromium.org | 2013-02-16 10:27:07 -0800 (Sat, 16 Feb 2013) | 10 lines
+
+[chromium] initialize all variables of TestRunner classes
+https://bugs.webkit.org/show_bug.cgi?id=110013
+
+Reviewed by Adam Barth.
+
+* DumpRenderTree/chromium/TestRunner/src/TestInterfaces.cpp:
+(WebTestRunner::TestInterfaces::TestInterfaces):
+* DumpRenderTree/chromium/TestRunner/src/TestRunner.cpp:
+(WebTestRunner::TestRunner::TestRunner):
+
+------------------------------------------------------------------------"""),
+ "https://trac.webkit.org/r143106 by Jochen Eisinger (jochen__) [chromium] initialize all variables of TestRunner classes"
+ " https://webkit.org/b/110013 Reviewed by Adam Barth (abarth).")
+
+ self.assertEqual(NewCommitBot._summarize_commit_log("""------------------------------------------------------------------------
+r140066 | simon.fraser@apple.com | 2013-01-17 16:10:31 -0800 (Thu, 17 Jan 2013) | 10 lines
+
+Allow PaintInfo to carry all PaintBehavior flags
+https://bugs.webkit.org/show_bug.cgi?id=106980
+
+Reviewed by Beth Dakin.
+
+In r139908 I missed one instance of the PaintInfo constructor that should take PaintBehaviorNormal
+instead of "false".
+
+* rendering/RenderScrollbarPart.cpp:
+(WebCore::RenderScrollbarPart::paintIntoRect):
+------------------------------------------------------------------------"""),
+ "https://trac.webkit.org/r140066 by Simon Fraser (smfr)"
+ " Allow PaintInfo to carry all PaintBehavior flags https://webkit.org/b/106980 Reviewed by Beth Dakin (dethbakin).")
+
+ def test_summarize_commit_log_rollout(self):
+ self.assertEqual(NewCommitBot._summarize_commit_log("""------------------------------------------------------------------------
+r143104 | commit-queue@webkit.org | 2013-02-16 09:09:01 -0800 (Sat, 16 Feb 2013) | 27 lines
+
+Unreviewed, rolling out r142734.
+http://trac.webkit.org/changeset/142734
+https://bugs.webkit.org/show_bug.cgi?id=110018
+
+"Triggered crashes on lots of websites" (Requested by ggaren
+on #webkit).
+
+Patch by Sheriff Bot <webkit.review.bot@gmail.com> on 2013-02-16
+
+Source/WebCore:
+
+------------------------------------------------------------------------"""),
+ "Geoffrey Garen (ggaren) rolled out r142734 in https://trac.webkit.org/r143104 : Triggered crashes on lots of websites")
+
+ self.assertEqual(NewCommitBot._summarize_commit_log("""------------------------------------------------------------------------
+r139884 | kov@webkit.org | 2013-01-16 08:26:10 -0800 (Wed, 16 Jan 2013) | 23 lines
+
+[GStreamer][Soup] Let GStreamer provide the buffer data is downloaded to, to avoid copying
+https://bugs.webkit.org/show_bug.cgi?id=105552
+
+Reverting 139877. It made a couple of API tests fail.
+
+* platform/graphics/gstreamer/GStreamerVersioning.cpp:
+* platform/graphics/gstreamer/GStreamerVersioning.h:
+* platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp:
+(StreamingClient):
+(_WebKitWebSrcPrivate):
+
+------------------------------------------------------------------------"""),
+ "Gustavo Noronha Silva (kov) rolled out 139877 in https://trac.webkit.org/r139884"
+ " [GStreamer][Soup] Let GStreamer provide the buffer data is downloaded to, to avoid copying"
+ " https://webkit.org/b/105552 It made a couple of API tests fail.")
+
+ self.assertEqual(NewCommitBot._summarize_commit_log("""------------------------------------------------------------------------
+r135487 | commit-queue@webkit.org | 2012-11-22 00:09:25 -0800 (Thu, 22 Nov 2012) | 52 lines
+
+Unreviewed, rolling out r134927 and r134944.
+http://trac.webkit.org/changeset/134927
+http://trac.webkit.org/changeset/134944
+https://bugs.webkit.org/show_bug.cgi?id=103028
+
+Reverting the reverts after merging. (Requested by vsevik on
+#webkit).
+
+Patch by Sheriff Bot <webkit.review.bot@gmail.com> on 2012-11-22
+
+* English.lproj/localizedStrings.js:
+* WebCore.gypi:
+* WebCore.vcproj/WebCore.vcproj:
+* inspector/compile-front-end.py:
+* inspector/front-end/AdvancedSearchController.js:
+* inspector/front-end/CallStackSidebarPane.js:
+
+------------------------------------------------------------------------"""),
+ "Vsevolod Vlasov (vsevik) rolled out r134927 and r134944 in https://trac.webkit.org/r135487 :"
+ " Reverting the reverts after merging.")
diff --git a/Tools/Scripts/webkitpy/tool/commands/openbugs.py b/Tools/Scripts/webkitpy/tool/commands/openbugs.py
index 8c55aba14..b2ed532e6 100644
--- a/Tools/Scripts/webkitpy/tool/commands/openbugs.py
+++ b/Tools/Scripts/webkitpy/tool/commands/openbugs.py
@@ -30,12 +30,12 @@ import logging
import re
import sys
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
_log = logging.getLogger(__name__)
-class OpenBugs(AbstractDeclarativeCommand):
+class OpenBugs(Command):
name = "open-bugs"
help_text = "Finds all bug numbers passed in arguments (or stdin if no args provided) and opens them in a web browser"
diff --git a/Tools/Scripts/webkitpy/tool/commands/perfalizer_unittest.py b/Tools/Scripts/webkitpy/tool/commands/perfalizer_unittest.py
index feb7b05b3..3efb46129 100644
--- a/Tools/Scripts/webkitpy/tool/commands/perfalizer_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/perfalizer_unittest.py
@@ -26,12 +26,12 @@
# (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
+import unittest2 as unittest
from webkitpy.common.net.buildbot import Builder
from webkitpy.common.system.executive import ScriptError
from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.layout_tests.port.test import TestPort
+from webkitpy.port.test import TestPort
from webkitpy.tool.commands.perfalizer import PerfalizerTask
from webkitpy.tool.mocktool import MockTool
diff --git a/Tools/Scripts/webkitpy/tool/commands/queries.py b/Tools/Scripts/webkitpy/tool/commands/queries.py
index 7cc846715..ff1b46ef2 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queries.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queries.py
@@ -1,6 +1,7 @@
# Copyright (c) 2009 Google Inc. All rights reserved.
# Copyright (c) 2009 Apple Inc. All rights reserved.
# Copyright (c) 2012 Intel Corporation. All rights reserved.
+# Copyright (c) 2013 University of Szeged. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -41,33 +42,31 @@ from webkitpy.common.checkout.commitinfo import CommitInfo
from webkitpy.common.config.committers import CommitterList
import webkitpy.common.config.urls as config_urls
from webkitpy.common.net.buildbot import BuildBot
+from webkitpy.common.net.bugzilla import Bugzilla
from webkitpy.common.net.regressionwindow import RegressionWindow
from webkitpy.common.system.crashlogs import CrashLogs
from webkitpy.common.system.user import User
+from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
from webkitpy.tool.grammar import pluralize
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
from webkitpy.layout_tests.models.test_expectations import TestExpectations
-from webkitpy.layout_tests.port import platform_options, configuration_options
+from webkitpy.port import platform_options, configuration_options
_log = logging.getLogger(__name__)
-class SuggestReviewers(AbstractDeclarativeCommand):
+class SuggestReviewers(AbstractSequencedCommand):
name = "suggest-reviewers"
help_text = "Suggest reviewers for a patch based on recent changes to the modified files."
+ steps = [
+ steps.SuggestReviewers,
+ ]
- def __init__(self):
- options = [
- steps.Options.git_commit,
- ]
- AbstractDeclarativeCommand.__init__(self, options=options)
-
- def execute(self, options, args, tool):
- reviewers = tool.checkout().suggested_reviewers(options.git_commit)
- print "\n".join([reviewer.full_name for reviewer in reviewers])
+ def _prepare_state(self, options, args, tool):
+ options.suggest_reviewers = True
-class BugsToCommit(AbstractDeclarativeCommand):
+class BugsToCommit(Command):
name = "bugs-to-commit"
help_text = "List bugs in the commit-queue"
@@ -78,7 +77,7 @@ class BugsToCommit(AbstractDeclarativeCommand):
print "%s" % bug_id
-class PatchesInCommitQueue(AbstractDeclarativeCommand):
+class PatchesInCommitQueue(Command):
name = "patches-in-commit-queue"
help_text = "List patches in the commit-queue"
@@ -89,14 +88,14 @@ class PatchesInCommitQueue(AbstractDeclarativeCommand):
print patch.url()
-class PatchesToCommitQueue(AbstractDeclarativeCommand):
+class PatchesToCommitQueue(Command):
name = "patches-to-commit-queue"
help_text = "Patches which should be added to the commit queue"
def __init__(self):
options = [
make_option("--bugs", action="store_true", dest="bugs", help="Output bug links instead of patch links"),
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
@staticmethod
def _needs_commit_queue(patch):
@@ -123,7 +122,7 @@ class PatchesToCommitQueue(AbstractDeclarativeCommand):
print "%s" % tool.bugs.attachment_url_for_id(patch.id(), action="edit")
-class PatchesToReview(AbstractDeclarativeCommand):
+class PatchesToReview(Command):
name = "patches-to-review"
help_text = "List bugs which have attachments pending review"
@@ -136,7 +135,7 @@ class PatchesToReview(AbstractDeclarativeCommand):
make_option("--cc-email",
help="Specifies the email on the CC field (defaults to your bugzilla login email)"),
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
def _print_report(self, report, cc_email, print_all):
if print_all:
@@ -176,7 +175,8 @@ class PatchesToReview(AbstractDeclarativeCommand):
report = self._generate_report(bugs, options.include_cq_denied)
self._print_report(report, cc_email, options.all)
-class WhatBroke(AbstractDeclarativeCommand):
+
+class WhatBroke(Command):
name = "what-broke"
help_text = "Print failing buildbots (%s) and what revisions broke them" % config_urls.buildbot_url
@@ -222,7 +222,7 @@ class WhatBroke(AbstractDeclarativeCommand):
print "All builders are passing!"
-class ResultsFor(AbstractDeclarativeCommand):
+class ResultsFor(Command):
name = "results-for"
help_text = "Print a list of failures for the passed revision from bots on %s" % config_urls.buildbot_url
argument_names = "REVISION"
@@ -244,7 +244,7 @@ class ResultsFor(AbstractDeclarativeCommand):
self._print_layout_test_results(build.layout_test_results())
-class FailureReason(AbstractDeclarativeCommand):
+class FailureReason(Command):
name = "failure-reason"
help_text = "Lists revisions where individual test failures started at %s" % config_urls.buildbot_url
@@ -330,7 +330,7 @@ class FailureReason(AbstractDeclarativeCommand):
return self._explain_failures_for_builder(builder, start_revision=int(start_revision))
-class FindFlakyTests(AbstractDeclarativeCommand):
+class FindFlakyTests(Command):
name = "find-flaky-tests"
help_text = "Lists tests that often fail for a single build at %s" % config_urls.buildbot_url
@@ -399,7 +399,7 @@ class FindFlakyTests(AbstractDeclarativeCommand):
return self._walk_backwards_from(builder, latest_revision, limit=int(limit))
-class TreeStatus(AbstractDeclarativeCommand):
+class TreeStatus(Command):
name = "tree-status"
help_text = "Print the status of the %s buildbots" % config_urls.buildbot_url
long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder
@@ -411,7 +411,7 @@ and displayes the status of each builder."""
print "%s : %s" % (status_string.ljust(4), builder["name"])
-class CrashLog(AbstractDeclarativeCommand):
+class CrashLog(Command):
name = "crash-log"
help_text = "Print the newest crash log for the given process"
long_help = """Finds the newest crash log matching the given process name
@@ -426,7 +426,7 @@ and PID and prints it to stdout."""
print crash_logs.find_newest_log(args[0], pid)
-class PrintExpectations(AbstractDeclarativeCommand):
+class PrintExpectations(Command):
name = 'print-expectations'
help_text = 'Print the expected result for the given test(s) on the given port(s)'
@@ -446,7 +446,7 @@ class PrintExpectations(AbstractDeclarativeCommand):
help='display the paths for all applicable expectation files'),
] + platform_options(use_globs=True)
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
self._expectation_models = {}
def execute(self, options, args, tool):
@@ -514,13 +514,10 @@ class PrintExpectations(AbstractDeclarativeCommand):
def _model(self, options, port_name, tests):
port = self._tool.port_factory.get(port_name, options)
- expectations_path = port.path_to_test_expectations_file()
- if not expectations_path in self._expectation_models:
- self._expectation_models[expectations_path] = TestExpectations(port, tests).model()
- return self._expectation_models[expectations_path]
+ return TestExpectations(port, tests).model()
-class PrintBaselines(AbstractDeclarativeCommand):
+class PrintBaselines(Command):
name = 'print-baselines'
help_text = 'Prints the baseline locations for given test(s) on the given port(s)'
@@ -533,7 +530,7 @@ class PrintBaselines(AbstractDeclarativeCommand):
make_option('--include-virtual-tests', action='store_true',
help='Include virtual tests'),
] + platform_options(use_globs=True)
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
self._platform_regexp = re.compile('platform/([^\/]+)/(.+)')
def execute(self, options, args, tool):
@@ -579,3 +576,36 @@ class PrintBaselines(AbstractDeclarativeCommand):
if platform_matchobj:
return platform_matchobj.group(1)
return None
+
+
+class FindResolvedBugs(Command):
+ name = "find-resolved-bugs"
+ help_text = "Collect the RESOLVED bugs in the given TestExpectations file"
+ argument_names = "TEST_EXPECTATIONS_FILE"
+
+ def execute(self, options, args, tool):
+ filename = args[0]
+ if not tool.filesystem.isfile(filename):
+ print "The given path is not a file, please pass a valid path."
+ return
+
+ ids = set()
+ inputfile = tool.filesystem.open_text_file_for_reading(filename)
+ for line in inputfile:
+ result = re.search("(https://bugs\.webkit\.org/show_bug\.cgi\?id=|webkit\.org/b/)([0-9]+)", line)
+ if result:
+ ids.add(result.group(2))
+ inputfile.close()
+
+ resolved_ids = set()
+ num_of_bugs = len(ids)
+ bugzilla = Bugzilla()
+ for i, bugid in enumerate(ids, start=1):
+ bug = bugzilla.fetch_bug(bugid)
+ print "Checking bug %s \t [%d/%d]" % (bugid, i, num_of_bugs)
+ if not bug.is_open():
+ resolved_ids.add(bugid)
+
+ print "Resolved bugs in %s :" % (filename)
+ for bugid in resolved_ids:
+ print "https://bugs.webkit.org/show_bug.cgi?id=%s" % (bugid)
diff --git a/Tools/Scripts/webkitpy/tool/commands/queries_unittest.py b/Tools/Scripts/webkitpy/tool/commands/queries_unittest.py
index b252c0b0e..8800cac3b 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queries_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queries_unittest.py
@@ -27,13 +27,13 @@
# (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
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.net.bugzilla import Bugzilla
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.thirdparty.mock import Mock
-from webkitpy.layout_tests.port.test import TestPort
+from webkitpy.port.test import TestPort
from webkitpy.tool.commands.commandtest import CommandsTest
from webkitpy.tool.commands.queries import *
from webkitpy.tool.mocktool import MockTool, MockOptions
@@ -173,7 +173,7 @@ class PrintExpectationsTest(unittest.TestCase):
command.execute(options, tests, tool)
finally:
stdout, _, _ = oc.restore_output()
- self.assertEqual(stdout, expected_stdout)
+ self.assertMultiLineEqual(stdout, expected_stdout)
def test_basic(self):
self.run_test(['failures/expected/text.html', 'failures/expected/image.html'],
@@ -223,10 +223,19 @@ class PrintExpectationsTest(unittest.TestCase):
def test_paths(self):
self.run_test([],
- ('LayoutTests/platform/test/TestExpectations\n'
+ ('LayoutTests/TestExpectations\n'
+ 'LayoutTests/platform/test/TestExpectations\n'
'LayoutTests/platform/test-win-xp/TestExpectations\n'),
paths=True)
+ def test_platform(self):
+ self.run_test(['platform/test-mac-leopard/http/test.html'],
+ ('// For test-mac-snowleopard\n'
+ 'platform/test-mac-leopard [ Pass Skip WontFix ]\n' # Note that this is the expectation (from being skipped internally), not the test name
+ '\n'
+ '// For test-mac-leopard\n'
+ 'platform/test-mac-leopard/http/test.html [ Pass ]\n'),
+ platform='test-mac-*')
class PrintBaselinesTest(unittest.TestCase):
def setUp(self):
@@ -255,7 +264,7 @@ class PrintBaselinesTest(unittest.TestCase):
self.capture_output()
command.execute(MockOptions(all=False, include_virtual_tests=False, csv=False, platform=None), ['passes/text.html'], self.tool)
stdout, _, _ = self.restore_output()
- self.assertEqual(stdout,
+ self.assertMultiLineEqual(stdout,
('// For test-win-xp\n'
'passes/text-expected.png\n'
'passes/text-expected.txt\n'))
@@ -266,7 +275,7 @@ class PrintBaselinesTest(unittest.TestCase):
self.capture_output()
command.execute(MockOptions(all=False, include_virtual_tests=False, csv=False, platform='test-win-*'), ['passes/text.html'], self.tool)
stdout, _, _ = self.restore_output()
- self.assertEqual(stdout,
+ self.assertMultiLineEqual(stdout,
('// For test-win-vista\n'
'passes/text-expected.png\n'
'passes/text-expected.txt\n'
@@ -285,6 +294,6 @@ class PrintBaselinesTest(unittest.TestCase):
self.capture_output()
command.execute(MockOptions(all=False, platform='*xp', csv=True, include_virtual_tests=False), ['passes/text.html'], self.tool)
stdout, _, _ = self.restore_output()
- self.assertEqual(stdout,
+ self.assertMultiLineEqual(stdout,
('test-win-xp,passes/text.html,None,png,passes/text-expected.png,None\n'
'test-win-xp,passes/text.html,None,txt,passes/text-expected.txt,None\n'))
diff --git a/Tools/Scripts/webkitpy/tool/commands/queues.py b/Tools/Scripts/webkitpy/tool/commands/queues.py
index edfbee402..74724cffb 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queues.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queues.py
@@ -72,7 +72,8 @@ class AbstractQueue(Command, QueueEngineDelegate):
make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue. Dangerous!"),
make_option("--exit-after-iteration", action="store", type="int", dest="iterations", default=None, help="Stop running the queue after iterating this number of times."),
]
- Command.__init__(self, "Run the %s" % self.name, options=options_list)
+ self.help_text = "Run the %s" % self.name
+ Command.__init__(self, options=options_list)
self._iteration_count = 0
def _cc_watchers(self, bug_id):
@@ -94,12 +95,17 @@ class AbstractQueue(Command, QueueEngineDelegate):
if self._options.port:
webkit_patch_args += ["--port=%s" % self._options.port]
webkit_patch_args.extend(args)
- # FIXME: There is probably no reason to use run_and_throw_if_fail anymore.
- # run_and_throw_if_fail was invented to support tee'd output
- # (where we write both to a log file and to the console at once),
- # but the queues don't need live-progress, a dump-of-output at the
- # end should be sufficient.
- return self._tool.executive.run_and_throw_if_fail(webkit_patch_args, cwd=self._tool.scm().checkout_root)
+
+ try:
+ args_for_printing = list(webkit_patch_args)
+ args_for_printing[0] = 'webkit-patch' # Printing our path for each log is redundant.
+ _log.info("Running: %s" % self._tool.executive.command_for_printing(args_for_printing))
+ command_output = self._tool.executive.run_command(webkit_patch_args, cwd=self._tool.scm().checkout_root)
+ except ScriptError, e:
+ # Make sure the whole output gets printed if the command failed.
+ _log.error(e.message_with_output(output_limit=None))
+ raise
+ return command_output
def _log_directory(self):
return os.path.join("..", "%s-logs" % self.name)
@@ -143,7 +149,7 @@ class AbstractQueue(Command, QueueEngineDelegate):
def execute(self, options, args, tool, engine=QueueEngine):
self._options = options # FIXME: This code is wrong. Command.options is a list, this assumes an Options element!
self._tool = tool # FIXME: This code is wrong too! Command.bind_to_tool handles this!
- return engine(self.name, self, self._tool.wakeup_event).run()
+ return engine(self.name, self, self._tool.wakeup_event, self._options.seconds_to_sleep).run()
@classmethod
def _log_from_script_error_for_upload(cls, script_error, output_limit=None):
@@ -241,10 +247,44 @@ class AbstractPatchQueue(AbstractQueue):
self._update_status(message, patch)
self._release_work_item(patch)
- # FIXME: This probably belongs at a layer below AbstractPatchQueue, but shared by CommitQueue and the EarlyWarningSystem.
+ def work_item_log_path(self, patch):
+ return os.path.join(self._log_directory(), "%s.log" % patch.bug_id())
+
+
+# Used to share code between the EWS and commit-queue.
+class PatchProcessingQueue(AbstractPatchQueue):
+ # Subclasses must override.
+ port_name = None
+
+ def __init__(self, options=None):
+ self._port = None # We can't instantiate port here because tool isn't avaialble.
+ AbstractPatchQueue.__init__(self, options)
+
+ # FIXME: This is a hack to map between the old port names and the new port names.
+ def _new_port_name_from_old(self, port_name, platform):
+ # ApplePort.determine_full_port_name asserts if the name doesn't include version.
+ if port_name == 'mac':
+ return 'mac-' + platform.os_version
+ if port_name == 'win':
+ return 'win-future'
+ return port_name
+
+ def begin_work_queue(self):
+ AbstractPatchQueue.begin_work_queue(self)
+ if not self.port_name:
+ return
+ # FIXME: This is only used for self._deprecated_port.flag()
+ self._deprecated_port = DeprecatedPort.port(self.port_name)
+ # FIXME: This violates abstraction
+ self._tool._deprecated_port = self._deprecated_port
+ self._port = self._tool.port_factory.get(self._new_port_name_from_old(self.port_name, self._tool.platform))
+
def _upload_results_archive_for_patch(self, patch, results_archive_zip):
+ if not self._port:
+ self._port = self._tool.port_factory.get(self._new_port_name_from_old(self.port_name, self._tool.platform))
+
bot_id = self._tool.status_server.bot_id or "bot"
- description = "Archive of layout-test-results from %s" % bot_id
+ description = "Archive of layout-test-results from %s for %s" % (bot_id, self._port.name())
# results_archive is a ZipFile object, grab the File object (.fp) to pass to Mechanize for uploading.
results_archive_file = results_archive_zip.fp
# Rewind the file object to start (since Mechanize won't do that automatically)
@@ -255,30 +295,21 @@ class AbstractPatchQueue(AbstractQueue):
comment_text = "The attached test failures were seen while running run-webkit-tests on the %s.\n" % (self.name)
# FIXME: We could easily list the test failures from the archive here,
# currently callers do that separately.
- comment_text += BotInfo(self._tool).summary_text()
+ comment_text += BotInfo(self._tool, self._port.name()).summary_text()
self._tool.bugs.add_attachment_to_bug(patch.bug_id(), results_archive_file, description, filename="layout-test-results.zip", comment_text=comment_text)
- def work_item_log_path(self, patch):
- return os.path.join(self._log_directory(), "%s.log" % patch.bug_id())
-
-class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskDelegate):
+class CommitQueue(PatchProcessingQueue, StepSequenceErrorHandler, CommitQueueTaskDelegate):
name = "commit-queue"
- port_name = "chromium-xvfb"
-
- def __init__(self):
- AbstractPatchQueue.__init__(self)
- self.port = DeprecatedPort.port(self.port_name)
+ port_name = "mac-mountainlion"
# AbstractPatchQueue methods
def begin_work_queue(self):
- # FIXME: This violates abstraction
- self._tool._deprecated_port = self.port
- AbstractPatchQueue.begin_work_queue(self)
+ PatchProcessingQueue.begin_work_queue(self)
self.committer_validator = CommitterValidator(self._tool)
self._expected_failures = ExpectedFailures()
- self._layout_test_results_reader = LayoutTestResultsReader(self._tool, self._log_directory())
+ self._layout_test_results_reader = LayoutTestResultsReader(self._tool, self._port.results_directory(), self._log_directory())
def next_work_item(self):
return self._next_patch()
@@ -319,7 +350,7 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskD
# CommitQueueTaskDelegate methods
def run_command(self, command):
- self.run_webkit_patch(command + [self.port.flag()])
+ self.run_webkit_patch(command + [self._deprecated_port.flag()])
def command_passed(self, message, patch):
self._update_status(message, patch=patch)
@@ -348,10 +379,10 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskD
reporter.report_flaky_tests(patch, flaky_test_results, results_archive)
def did_pass_testing_ews(self, patch):
- # Currently, chromium-ews is the only testing EWS. Once there are more,
- # should make sure they all pass.
- status = self._tool.status_server.patch_status("chromium-ews", patch.id())
- return status == self._pass_status
+ # Only Mac and Mac WK2 run tests
+ # FIXME: We shouldn't have to hard-code it here.
+ patch_status = self._tool.status_server.patch_status
+ return patch_status("mac-ews", patch.id()) == self._pass_status or patch_status("mac-wk2-ews", patch.id()) == self._pass_status
# StepSequenceErrorHandler methods
@@ -376,10 +407,10 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskD
raise TryAgain()
-class AbstractReviewQueue(AbstractPatchQueue, StepSequenceErrorHandler):
+class AbstractReviewQueue(PatchProcessingQueue, StepSequenceErrorHandler):
"""This is the base-class for the EWS queues and the style-queue."""
def __init__(self, options=None):
- AbstractPatchQueue.__init__(self, options)
+ PatchProcessingQueue.__init__(self, options)
def review_patch(self, patch):
raise NotImplementedError("subclasses must implement")
@@ -387,7 +418,7 @@ class AbstractReviewQueue(AbstractPatchQueue, StepSequenceErrorHandler):
# AbstractPatchQueue methods
def begin_work_queue(self):
- AbstractPatchQueue.begin_work_queue(self)
+ PatchProcessingQueue.begin_work_queue(self)
def next_work_item(self):
return self._next_patch()
diff --git a/Tools/Scripts/webkitpy/tool/commands/queues_unittest.py b/Tools/Scripts/webkitpy/tool/commands/queues_unittest.py
index 0a32f29be..a09164dde 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queues_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queues_unittest.py
@@ -88,7 +88,7 @@ class AbstractQueueTest(CommandsTest):
if port:
expected_run_args.append("--port=%s" % port)
expected_run_args.extend(run_args)
- tool.executive.run_and_throw_if_fail.assert_called_with(expected_run_args, cwd='/mock-checkout')
+ tool.executive.run_command.assert_called_with(expected_run_args, cwd='/mock-checkout')
def test_run_webkit_patch(self):
self._assert_run_webkit_patch([1])
@@ -134,7 +134,7 @@ class FeederQueueTest(QueuesTest):
"begin_work_queue": self._default_begin_work_queue_logs("feeder-queue"),
"process_work_item": """Warning, attachment 10001 on bug 50000 has invalid committer (non-committer@example.com)
Warning, attachment 10001 on bug 50000 has invalid committer (non-committer@example.com)
-MOCK setting flag 'commit-queue' to '-' on attachment '10001' with comment 'Rejecting attachment 10001 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/Tools/Scripts/webkitpy/common/config/committers.py.
+MOCK setting flag 'commit-queue' to '-' on attachment '10001' with comment 'Rejecting attachment 10001 from commit-queue.\n\nnon-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/Tools/Scripts/webkitpy/common/config/committers.py.
- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
@@ -156,7 +156,7 @@ class AbstractPatchQueueTest(CommandsTest):
queue.bind_to_tool(tool)
queue._options = Mock()
queue._options.port = None
- self.assertEqual(queue._next_patch(), None)
+ self.assertIsNone(queue._next_patch())
tool.status_server = MockStatusServer(work_items=[2, 10000, 10001])
expected_stdout = "MOCK: fetch_attachment: 2 is not a known attachment id\n" # A mock-only message to prevent us from making mistakes.
expected_logs = "MOCK: release_work_item: None 2\n"
@@ -166,18 +166,20 @@ class AbstractPatchQueueTest(CommandsTest):
self.assertEqual(queue._next_patch().id(), 10001)
self.assertEqual(queue._next_patch(), None) # When the queue is empty
+
+class PatchProcessingQueueTest(CommandsTest):
def test_upload_results_archive_for_patch(self):
- queue = AbstractPatchQueue()
+ queue = PatchProcessingQueue()
queue.name = "mock-queue"
tool = MockTool()
queue.bind_to_tool(tool)
queue._options = Mock()
queue._options.port = None
patch = queue._tool.bugs.fetch_attachment(10001)
- expected_logs = """MOCK add_attachment_to_bug: bug_id=50000, description=Archive of layout-test-results from bot filename=layout-test-results.zip mimetype=None
+ expected_logs = """MOCK add_attachment_to_bug: bug_id=50000, description=Archive of layout-test-results from bot for mac-snowleopard filename=layout-test-results.zip mimetype=None
-- Begin comment --
The attached test failures were seen while running run-webkit-tests on the mock-queue.
-Port: MockPort Platform: MockPlatform 1.0
+Port: mac-snowleopard Platform: MockPlatform 1.0
-- End comment --
"""
OutputCapture().assert_outputs(self, queue._upload_results_archive_for_patch, [patch, Mock()], expected_logs=expected_logs)
@@ -236,18 +238,25 @@ class CommitQueueTest(QueuesTest):
tool.filesystem.write_text_file('/tmp/layout-test-results/webkit_unit_tests_output.xml', '')
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs("commit-queue"),
- "process_work_item": """MOCK: update_status: commit-queue Cleaned working directory
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean --port=mac
+MOCK: update_status: commit-queue Cleaned working directory
+Running: webkit-patch --status-host=example.com update --port=mac
MOCK: update_status: commit-queue Updated working directory
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000 --port=mac
MOCK: update_status: commit-queue Applied patch
+Running: webkit-patch --status-host=example.com validate-changelog --check-oops --non-interactive 10000 --port=mac
MOCK: update_status: commit-queue ChangeLog validated
+Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=release --port=mac
MOCK: update_status: commit-queue Built patch
+Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --port=mac
MOCK: update_status: commit-queue Passed tests
+Running: webkit-patch --status-host=example.com land-attachment --force-clean --non-interactive --parent-command=commit-queue 10000 --port=mac
MOCK: update_status: commit-queue Landed patch
MOCK: update_status: commit-queue Pass
MOCK: release_work_item: commit-queue 10000
""",
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
- "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nMock error message'\n",
}
self.assert_queue_outputs(CommitQueue(), tool=tool, expected_logs=expected_logs)
@@ -257,13 +266,13 @@ MOCK: release_work_item: commit-queue 10000
"process_work_item": """MOCK: update_status: commit-queue Cleaned working directory
MOCK: update_status: commit-queue Updated working directory
MOCK: update_status: commit-queue Patch does not apply
-MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'MOCK script error
+MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nMOCK script error
Full output: http://dummy_url'
MOCK: update_status: commit-queue Fail
MOCK: release_work_item: commit-queue 10000
""",
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
- "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nMock error message'\n",
}
queue = CommitQueue()
@@ -283,7 +292,7 @@ MOCK: release_work_item: commit-queue 10000
"process_work_item": """MOCK: update_status: commit-queue Cleaned working directory
MOCK: update_status: commit-queue Updated working directory
MOCK: update_status: commit-queue Patch does not apply
-MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'New failing tests:
+MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nNew failing tests:
mock_test_name.html
another_test_name.html
Full output: http://dummy_url'
@@ -291,7 +300,7 @@ MOCK: update_status: commit-queue Fail
MOCK: release_work_item: commit-queue 10000
""",
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
- "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nMock error message'\n",
}
queue = CommitQueue()
@@ -307,56 +316,56 @@ MOCK: release_work_item: commit-queue 10000
self.assert_queue_outputs(queue, expected_logs=expected_logs)
def test_rollout(self):
- tool = MockTool(log_executive=True)
+ tool = MockTool()
tool.filesystem.write_text_file('/tmp/layout-test-results/full_results.json', '') # Otherwise the commit-queue will hit a KeyError trying to read the results from the MockFileSystem.
tool.filesystem.write_text_file('/tmp/layout-test-results/webkit_unit_tests_output.xml', '')
tool.buildbot.light_tree_on_fire()
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs("commit-queue"),
- "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'clean', '--port=%(port_name)s'], cwd=/mock-checkout
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean --port=%(port)s
MOCK: update_status: commit-queue Cleaned working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'update', '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com update --port=%(port)s
MOCK: update_status: commit-queue Updated working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--no-update', '--non-interactive', 10000, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000 --port=%(port)s
MOCK: update_status: commit-queue Applied patch
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'validate-changelog', '--non-interactive', 10000, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com validate-changelog --check-oops --non-interactive 10000 --port=%(port)s
MOCK: update_status: commit-queue ChangeLog validated
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--no-clean', '--no-update', '--build-style=release', '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=release --port=%(port)s
MOCK: update_status: commit-queue Built patch
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive', '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --port=%(port)s
MOCK: update_status: commit-queue Passed tests
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10000, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com land-attachment --force-clean --non-interactive --parent-command=commit-queue 10000 --port=%(port)s
MOCK: update_status: commit-queue Landed patch
MOCK: update_status: commit-queue Pass
MOCK: release_work_item: commit-queue 10000
-""" % {"port_name": CommitQueue.port_name},
+""" % {"port": "mac"},
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
- "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10000' with comment 'Rejecting attachment 10000 from commit-queue.\n\nMock error message'\n",
}
self.assert_queue_outputs(CommitQueue(), tool=tool, expected_logs=expected_logs)
def test_rollout_lands(self):
- tool = MockTool(log_executive=True)
+ tool = MockTool()
tool.buildbot.light_tree_on_fire()
rollout_patch = tool.bugs.fetch_attachment(10005) # _patch6, a rollout patch.
assert(rollout_patch.is_rollout())
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs("commit-queue"),
- "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'clean', '--port=%(port_name)s'], cwd=/mock-checkout
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean --port=%(port)s
MOCK: update_status: commit-queue Cleaned working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'update', '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com update --port=%(port)s
MOCK: update_status: commit-queue Updated working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--no-update', '--non-interactive', 10005, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10005 --port=%(port)s
MOCK: update_status: commit-queue Applied patch
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'validate-changelog', '--non-interactive', 10005, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com validate-changelog --check-oops --non-interactive 10005 --port=%(port)s
MOCK: update_status: commit-queue ChangeLog validated
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--parent-command=commit-queue', 10005, '--port=%(port_name)s'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com land-attachment --force-clean --non-interactive --parent-command=commit-queue 10005 --port=%(port)s
MOCK: update_status: commit-queue Landed patch
MOCK: update_status: commit-queue Pass
MOCK: release_work_item: commit-queue 10005
-""" % {"port_name": CommitQueue.port_name},
+""" % {"port": "mac"},
"handle_script_error": "ScriptError error message\n\nMOCK output\n",
- "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10005' with comment 'Rejecting attachment 10005 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '10005' with comment 'Rejecting attachment 10005 from commit-queue.\n\nMock error message'\n",
}
self.assert_queue_outputs(CommitQueue(), tool=tool, work_item=rollout_patch, expected_logs=expected_logs)
@@ -373,9 +382,9 @@ MOCK: update_status: commit-queue Tests passed, but commit failed (checkout out
state = {'patch': None}
OutputCapture().assert_outputs(self, sequence.run_and_handle_errors, [tool, options, state], expected_exception=TryAgain, expected_logs=expected_logs)
- self.assertEqual(options.update, True)
- self.assertEqual(options.build, False)
- self.assertEqual(options.test, False)
+ self.assertTrue(options.update)
+ self.assertFalse(options.build)
+ self.assertFalse(options.test)
def test_manual_reject_during_processing(self):
queue = SecondThoughtsCommitQueue(MockTool())
@@ -384,15 +393,22 @@ MOCK: update_status: commit-queue Tests passed, but commit failed (checkout out
queue._tool.filesystem.write_text_file('/tmp/layout-test-results/webkit_unit_tests_output.xml', '')
queue._options = Mock()
queue._options.port = None
- expected_logs = """MOCK: update_status: commit-queue Cleaned working directory
+ expected_logs = """Running: webkit-patch --status-host=example.com clean --port=mac
+MOCK: update_status: commit-queue Cleaned working directory
+Running: webkit-patch --status-host=example.com update --port=mac
MOCK: update_status: commit-queue Updated working directory
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000 --port=mac
MOCK: update_status: commit-queue Applied patch
+Running: webkit-patch --status-host=example.com validate-changelog --check-oops --non-interactive 10000 --port=mac
MOCK: update_status: commit-queue ChangeLog validated
+Running: webkit-patch --status-host=example.com build --no-clean --no-update --build-style=release --port=mac
MOCK: update_status: commit-queue Built patch
+Running: webkit-patch --status-host=example.com build-and-test --no-clean --no-update --test --non-interactive --port=mac
MOCK: update_status: commit-queue Passed tests
MOCK: update_status: commit-queue Retry
MOCK: release_work_item: commit-queue 10000
"""
+ self.maxDiff = None
OutputCapture().assert_outputs(self, queue.process_work_item, [QueuesTest.mock_work_item], expected_logs=expected_logs)
def test_report_flaky_tests(self):
@@ -449,15 +465,15 @@ class StyleQueueTest(QueuesTest):
def test_style_queue_with_style_exception(self):
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs("style-queue"),
- "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'clean'], cwd=/mock-checkout
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean
MOCK: update_status: style-queue Cleaned working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'update'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com update
MOCK: update_status: style-queue Updated working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--no-update', '--non-interactive', 10000], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000
MOCK: update_status: style-queue Applied patch
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-watchlist-local', 50000], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-watchlist-local 50000
MOCK: update_status: style-queue Watchlist applied
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'check-style-local', '--non-interactive', '--quiet'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com check-style-local --non-interactive --quiet
MOCK: update_status: style-queue Style checked
MOCK: update_status: style-queue Pass
MOCK: release_work_item: style-queue 10000
@@ -465,21 +481,24 @@ MOCK: release_work_item: style-queue 10000
"handle_unexpected_error": "Mock error message\n",
"handle_script_error": "MOCK output\n",
}
- tool = MockTool(log_executive=True, executive_throws_when_run=set(['check-style']))
+ tool = MockTool(executive_throws_when_run=set(['check-style']))
self.assert_queue_outputs(StyleQueue(), expected_logs=expected_logs, tool=tool)
def test_style_queue_with_watch_list_exception(self):
expected_logs = {
"begin_work_queue": self._default_begin_work_queue_logs("style-queue"),
- "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'clean'], cwd=/mock-checkout
+ "process_work_item": """Running: webkit-patch --status-host=example.com clean
MOCK: update_status: style-queue Cleaned working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'update'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com update
MOCK: update_status: style-queue Updated working directory
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--no-update', '--non-interactive', 10000], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-attachment --no-update --non-interactive 10000
MOCK: update_status: style-queue Applied patch
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-watchlist-local', 50000], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com apply-watchlist-local 50000
+Exception for ['echo', '--status-host=example.com', 'apply-watchlist-local', 50000]
+
+MOCK command output
MOCK: update_status: style-queue Unabled to apply watchlist
-MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'check-style-local', '--non-interactive', '--quiet'], cwd=/mock-checkout
+Running: webkit-patch --status-host=example.com check-style-local --non-interactive --quiet
MOCK: update_status: style-queue Style checked
MOCK: update_status: style-queue Pass
MOCK: release_work_item: style-queue 10000
@@ -487,5 +506,5 @@ MOCK: release_work_item: style-queue 10000
"handle_unexpected_error": "Mock error message\n",
"handle_script_error": "MOCK output\n",
}
- tool = MockTool(log_executive=True, executive_throws_when_run=set(['apply-watchlist-local']))
+ tool = MockTool(executive_throws_when_run=set(['apply-watchlist-local']))
self.assert_queue_outputs(StyleQueue(), expected_logs=expected_logs, tool=tool)
diff --git a/Tools/Scripts/webkitpy/tool/commands/queuestest.py b/Tools/Scripts/webkitpy/tool/commands/queuestest.py
index 314a64021..c633b8479 100644
--- a/Tools/Scripts/webkitpy/tool/commands/queuestest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/queuestest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.net.bugzilla import Attachment
from webkitpy.common.system.outputcapture import OutputCapture
@@ -37,7 +37,7 @@ from webkitpy.tool.mocktool import MockTool
class MockQueueEngine(object):
- def __init__(self, name, queue, wakeup_event):
+ def __init__(self, name, queue, wakeup_event, seconds_to_sleep):
pass
def run(self):
@@ -79,7 +79,7 @@ class QueuesTest(unittest.TestCase):
tool = MockTool()
# This is a hack to make it easy for callers to not have to setup a custom MockFileSystem just to test the commit-queue
# the cq tries to read the layout test results, and will hit a KeyError in MockFileSystem if we don't do this.
- tool.filesystem.write_text_file('/mock-results/results.html', "")
+ tool.filesystem.write_text_file('/mock-results/full_results.json', "")
if not expected_stdout:
expected_stdout = {}
if not expected_stderr:
diff --git a/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
index d9209b118..06d42d097 100644
--- a/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
+++ b/Tools/Scripts/webkitpy/tool/commands/rebaseline.py
@@ -36,9 +36,9 @@ from webkitpy.common.system.executive import ScriptError
from webkitpy.layout_tests.controllers.test_result_writer import TestResultWriter
from webkitpy.layout_tests.models import test_failures
from webkitpy.layout_tests.models.test_expectations import TestExpectations, BASELINE_SUFFIX_LIST
-from webkitpy.layout_tests.port import builders
-from webkitpy.layout_tests.port import factory
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.port import builders
+from webkitpy.port import factory
+from webkitpy.tool.multicommandtool import Command
_log = logging.getLogger(__name__)
@@ -49,8 +49,8 @@ def _baseline_name(fs, test_name, suffix):
return fs.splitext(test_name)[0] + TestResultWriter.FILENAME_SUFFIX_EXPECTED + "." + suffix
-class AbstractRebaseliningCommand(AbstractDeclarativeCommand):
- # not overriding execute() - pylint: disable-msg=W0223
+class AbstractRebaseliningCommand(Command):
+ # not overriding execute() - pylint: disable=W0223
move_overwritten_baselines_option = optparse.make_option("--move-overwritten-baselines", action="store_true", default=False,
help="Move overwritten baselines elsewhere in the baseline path. This is for bringing up new ports.")
@@ -88,7 +88,7 @@ class RebaselineTest(AbstractRebaseliningCommand):
self._scm_changes = {'add': []}
def _results_url(self, builder_name):
- return self._tool.buildbot_for_builder_name(builder_name).builder_with_name(builder_name).latest_layout_test_results_url()
+ return self._tool.buildbot.builder_with_name(builder_name).latest_layout_test_results_url()
def _baseline_directory(self, builder_name):
port = self._tool.port_factory.get_from_builder_name(builder_name)
@@ -152,7 +152,7 @@ class RebaselineTest(AbstractRebaseliningCommand):
path = port.path_to_test_expectations_file()
lock = self._tool.make_file_lock(path + '.lock')
lock.acquire_lock()
- expectations = TestExpectations(port, include_overrides=False)
+ expectations = TestExpectations(port, include_generic=False, include_overrides=False)
for test_configuration in port.all_test_configurations():
if test_configuration.version == port.test_configuration().version:
expectationsString = expectations.remove_configuration_from_test(test_name, test_configuration)
@@ -266,7 +266,7 @@ class AnalyzeBaselines(AbstractRebaseliningCommand):
class AbstractParallelRebaselineCommand(AbstractRebaseliningCommand):
- # not overriding execute() - pylint: disable-msg=W0223
+ # not overriding execute() - pylint: disable=W0223
def _run_webkit_patch(self, args, verbose):
try:
@@ -454,22 +454,18 @@ class Rebaseline(AbstractParallelRebaselineCommand):
])
def _builders_to_pull_from(self):
- chromium_buildbot_builder_names = []
webkit_buildbot_builder_names = []
for name in builders.all_builder_names():
- if self._tool.port_factory.get_from_builder_name(name).is_chromium():
- chromium_buildbot_builder_names.append(name)
- else:
- webkit_buildbot_builder_names.append(name)
+ webkit_buildbot_builder_names.append(name)
- titles = ["build.webkit.org bots", "build.chromium.org bots"]
- lists = [webkit_buildbot_builder_names, chromium_buildbot_builder_names]
+ titles = ["build.webkit.org bots"]
+ lists = [webkit_buildbot_builder_names]
chosen_names = self._tool.user.prompt_with_multiple_lists("Which builder to pull results from:", titles, lists, can_choose_multiple=True)
return [self._builder_with_name(name) for name in chosen_names]
def _builder_with_name(self, name):
- return self._tool.buildbot_for_builder_name(name).builder_with_name(name)
+ return self._tool.buildbot.builder_with_name(name)
def _tests_to_update(self, builder):
failing_tests = builder.latest_layout_test_results().tests_matching_failure_types([test_failures.FailureTextMismatch])
diff --git a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
index cc25fae2b..43a8786fe 100644
--- a/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.checkout.baselineoptimizer import BaselineOptimizer
@@ -39,15 +39,15 @@ from webkitpy.tool.mocktool import MockTool, MockOptions
class _BaseTestCase(unittest.TestCase):
MOCK_WEB_RESULT = 'MOCK Web result, convert 404 to None=True'
- WEB_PREFIX = 'http://example.com/f/builders/WebKit Mac10.7/results/layout-test-results'
+ WEB_PREFIX = 'http://example.com/f/builders/Apple Lion Release WK1 (Tests)/results/layout-test-results'
command_constructor = None
def setUp(self):
self.tool = MockTool()
- self.command = self.command_constructor() # lint warns that command_constructor might not be set, but this is intentional; pylint: disable-msg=E1102
+ self.command = self.command_constructor() # lint warns that command_constructor might not be set, but this is intentional; pylint: disable=E1102
self.command.bind_to_tool(self.tool)
- self.lion_port = self.tool.port_factory.get_from_builder_name("WebKit Mac10.7")
+ self.lion_port = self.tool.port_factory.get_from_builder_name("Apple Lion Release WK1 (Tests)")
self.lion_expectations_path = self.lion_port.path_to_test_expectations_file()
# FIXME: we should override builders._exact_matches here to point to a set
@@ -78,20 +78,21 @@ class TestRebaselineTest(_BaseTestCase):
def setUp(self):
super(TestRebaselineTest, self).setUp()
- self.options = MockOptions(builder="WebKit Mac10.7", test="userscripts/another-test.html", suffixes="txt",
+ self.options = MockOptions(builder="Apple Lion Release WK1 (Tests)", test="userscripts/another-test.html", suffixes="txt",
move_overwritten_baselines_to=None, results_directory=None)
def test_baseline_directory(self):
command = self.command
- self.assertEqual(command._baseline_directory("Apple Win XP Debug (Tests)"), "/mock-checkout/LayoutTests/platform/win-xp")
- self.assertEqual(command._baseline_directory("Apple Win 7 Release (Tests)"), "/mock-checkout/LayoutTests/platform/win")
- self.assertEqual(command._baseline_directory("Apple Lion Release WK1 (Tests)"), "/mock-checkout/LayoutTests/platform/mac-lion")
- self.assertEqual(command._baseline_directory("Apple Lion Release WK2 (Tests)"), "/mock-checkout/LayoutTests/platform/mac-wk2")
- self.assertEqual(command._baseline_directory("GTK Linux 32-bit Release"), "/mock-checkout/LayoutTests/platform/gtk")
- self.assertEqual(command._baseline_directory("EFL Linux 64-bit Release WK2"), "/mock-checkout/LayoutTests/platform/efl-wk2")
- self.assertEqual(command._baseline_directory("Qt Linux Release"), "/mock-checkout/LayoutTests/platform/qt")
- self.assertEqual(command._baseline_directory("WebKit Mac10.7"), "/mock-checkout/LayoutTests/platform/chromium-mac-lion")
- self.assertEqual(command._baseline_directory("WebKit Mac10.6"), "/mock-checkout/LayoutTests/platform/chromium-mac-snowleopard")
+ self.assertMultiLineEqual(command._baseline_directory("Apple Win XP Debug (Tests)"), "/mock-checkout/LayoutTests/platform/win-xp")
+ self.assertMultiLineEqual(command._baseline_directory("Apple Win 7 Release (Tests)"), "/mock-checkout/LayoutTests/platform/win")
+ self.assertMultiLineEqual(command._baseline_directory("Apple Lion Release WK1 (Tests)"), "/mock-checkout/LayoutTests/platform/mac-lion")
+ self.assertMultiLineEqual(command._baseline_directory("Apple Lion Release WK2 (Tests)"), "/mock-checkout/LayoutTests/platform/mac-wk2")
+ self.assertMultiLineEqual(command._baseline_directory("Apple MountainLion Release WK1 (Tests)"), "/mock-checkout/LayoutTests/platform/mac")
+ self.assertMultiLineEqual(command._baseline_directory("Apple MountainLion Release WK2 (Tests)"), "/mock-checkout/LayoutTests/platform/mac")
+ self.assertMultiLineEqual(command._baseline_directory("GTK Linux 64-bit Debug"), "/mock-checkout/LayoutTests/platform/gtk-wk1")
+ self.assertMultiLineEqual(command._baseline_directory("GTK Linux 64-bit Release WK2 (Tests)"), "/mock-checkout/LayoutTests/platform/gtk-wk2")
+ self.assertMultiLineEqual(command._baseline_directory("EFL Linux 64-bit Release WK2"), "/mock-checkout/LayoutTests/platform/efl-wk2")
+ self.assertMultiLineEqual(command._baseline_directory("Qt Linux Release"), "/mock-checkout/LayoutTests/platform/qt")
def test_rebaseline_updates_expectations_file_noop(self):
self._zero_out_test_expectations()
@@ -105,12 +106,12 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
self.options.suffixes = "png,wav,txt"
self.command._rebaseline_test_and_update_expectations(self.options)
- self.assertEqual(self.tool.web.urls_fetched,
+ self.assertItemsEqual(self.tool.web.urls_fetched,
[self.WEB_PREFIX + '/userscripts/another-test-actual.png',
self.WEB_PREFIX + '/userscripts/another-test-actual.wav',
self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
new_expectations = self._read(self.lion_expectations_path)
- self.assertEqual(new_expectations, """Bug(B) [ Mac Linux XP Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]
+ self.assertMultiLineEqual(new_expectations, """Bug(B) [ Mac Linux XP Debug ] fast/dom/Window/window-postmessage-clone-really-deep-array.html [ Pass ]
Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
""")
@@ -121,79 +122,78 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
self.options.suffixes = 'png,wav,txt'
self.command._rebaseline_test_and_update_expectations(self.options)
- self.assertEqual(self.tool.web.urls_fetched,
+ self.assertItemsEqual(self.tool.web.urls_fetched,
[self.WEB_PREFIX + '/userscripts/another-test-actual.png',
self.WEB_PREFIX + '/userscripts/another-test-actual.wav',
self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
new_expectations = self._read(self.lion_expectations_path)
- self.assertEqual(new_expectations, "Bug(x) [ MountainLion SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
+ self.assertMultiLineEqual(new_expectations, "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
def test_rebaseline_does_not_include_overrides(self):
self._write(self.lion_expectations_path, "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
- self._write(self.lion_port.path_from_chromium_base('skia', 'skia_test_expectations.txt'), "Bug(y) [ Mac ] other-test.html [ Failure ]\n")
self._write("userscripts/another-test.html", "Dummy test contents")
self.options.suffixes = 'png,wav,txt'
self.command._rebaseline_test_and_update_expectations(self.options)
- self.assertEqual(self.tool.web.urls_fetched,
+ self.assertItemsEqual(self.tool.web.urls_fetched,
[self.WEB_PREFIX + '/userscripts/another-test-actual.png',
self.WEB_PREFIX + '/userscripts/another-test-actual.wav',
self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
new_expectations = self._read(self.lion_expectations_path)
- self.assertEqual(new_expectations, "Bug(x) [ MountainLion SnowLeopard ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
+ self.assertMultiLineEqual(new_expectations, "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nBug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
def test_rebaseline_test(self):
- self.command._rebaseline_test("WebKit Linux", "userscripts/another-test.html", None, "txt", self.WEB_PREFIX)
- self.assertEqual(self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", None, "txt", self.WEB_PREFIX)
+ self.assertItemsEqual(self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
def test_rebaseline_test_with_results_directory(self):
self._write(self.lion_expectations_path, "Bug(x) [ Mac ] userscripts/another-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/another-test.html [ ImageOnlyFailure ]\n")
self.options.results_directory = '/tmp'
self.command._rebaseline_test_and_update_expectations(self.options)
- self.assertEqual(self.tool.web.urls_fetched, ['file:///tmp/userscripts/another-test-actual.txt'])
+ self.assertItemsEqual(self.tool.web.urls_fetched, ['file:///tmp/userscripts/another-test-actual.txt'])
def test_rebaseline_test_and_print_scm_changes(self):
self.command._print_scm_changes = True
self.command._scm_changes = {'add': [], 'delete': []}
self.tool._scm.exists = lambda x: False
- self.command._rebaseline_test("WebKit Linux", "userscripts/another-test.html", None, "txt", None)
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", None, "txt", None)
- self.assertEqual(self.command._scm_changes, {'add': ['/mock-checkout/LayoutTests/platform/chromium-linux/userscripts/another-test-expected.txt'], 'delete': []})
+ self.assertDictEqual(self.command._scm_changes, {'add': ['/mock-checkout/LayoutTests/platform/mac-lion/userscripts/another-test-expected.txt'], 'delete': []})
def test_rebaseline_and_copy_test(self):
self._write("userscripts/another-test-expected.txt", "generic result")
- self.command._rebaseline_test("WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt", None)
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", ["mac-lion-wk2"], "txt", None)
- self.assertEqual(self._read('platform/chromium-mac-lion/userscripts/another-test-expected.txt'), self.MOCK_WEB_RESULT)
- self.assertEqual(self._read('platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt'), 'generic result')
+ self.assertMultiLineEqual(self._read('platform/mac-lion/userscripts/another-test-expected.txt'), self.MOCK_WEB_RESULT)
+ self.assertMultiLineEqual(self._read('platform/mac-wk2/userscripts/another-test-expected.txt'), 'generic result')
def test_rebaseline_and_copy_test_no_existing_result(self):
- self.command._rebaseline_test("WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt", None)
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", ["mac-lion-wk2"], "txt", None)
- self.assertEqual(self._read('platform/chromium-mac-lion/userscripts/another-test-expected.txt'), self.MOCK_WEB_RESULT)
- self.assertFalse(self.tool.filesystem.exists(self._expand('platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt')))
+ self.assertMultiLineEqual(self._read('platform/mac-lion/userscripts/another-test-expected.txt'), self.MOCK_WEB_RESULT)
+ self.assertFalse(self.tool.filesystem.exists(self._expand('platform/mac-lion-wk2/userscripts/another-test-expected.txt')))
def test_rebaseline_and_copy_test_with_lion_result(self):
- self._write("platform/chromium-mac-lion/userscripts/another-test-expected.txt", "original lion result")
+ self._write("platform/mac-lion/userscripts/another-test-expected.txt", "original lion result")
- self.command._rebaseline_test("WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt", self.WEB_PREFIX)
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", ["mac-lion-wk2"], "txt", self.WEB_PREFIX)
- self.assertEqual(self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
- self.assertEqual(self._read("platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt"), "original lion result")
- self.assertEqual(self._read("platform/chromium-mac-lion/userscripts/another-test-expected.txt"), self.MOCK_WEB_RESULT)
+ self.assertItemsEqual(self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
+ self.assertMultiLineEqual(self._read("platform/mac-wk2/userscripts/another-test-expected.txt"), "original lion result")
+ self.assertMultiLineEqual(self._read("platform/mac-lion/userscripts/another-test-expected.txt"), self.MOCK_WEB_RESULT)
def test_rebaseline_and_copy_no_overwrite_test(self):
- self._write("platform/chromium-mac-lion/userscripts/another-test-expected.txt", "original lion result")
- self._write("platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt", "original snowleopard result")
+ self._write("platform/mac-lion/userscripts/another-test-expected.txt", "original lion result")
+ self._write("platform/mac-lion-wk2/userscripts/another-test-expected.txt", "original lion wk2 result")
- self.command._rebaseline_test("WebKit Mac10.7", "userscripts/another-test.html", ["chromium-mac-snowleopard"], "txt", None)
+ self.command._rebaseline_test("Apple Lion Release WK1 (Tests)", "userscripts/another-test.html", ["mac-lion-wk2"], "txt", None)
- self.assertEqual(self._read("platform/chromium-mac-snowleopard/userscripts/another-test-expected.txt"), "original snowleopard result")
- self.assertEqual(self._read("platform/chromium-mac-lion/userscripts/another-test-expected.txt"), self.MOCK_WEB_RESULT)
+ self.assertMultiLineEqual(self._read("platform/mac-lion-wk2/userscripts/another-test-expected.txt"), "original lion wk2 result")
+ self.assertMultiLineEqual(self._read("platform/mac-lion/userscripts/another-test-expected.txt"), self.MOCK_WEB_RESULT)
def test_rebaseline_test_internal_with_move_overwritten_baselines_to(self):
self.tool.executive = MockExecutive2()
@@ -220,8 +220,8 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ]
out, _, _ = oc.restore_output()
builders._exact_matches = old_exact_matches
- self.assertEqual(self._read(self.tool.filesystem.join(port.layout_tests_dir(), 'platform/test-mac-leopard/failures/expected/image-expected.txt')), 'original snowleopard result')
- self.assertEqual(out, '{"add": []}\n')
+ self.assertMultiLineEqual(self._read(self.tool.filesystem.join(port.layout_tests_dir(), 'platform/test-mac-leopard/failures/expected/image-expected.txt')), 'original snowleopard result')
+ self.assertMultiLineEqual(out, '{"add": []}\n')
class TestRebaselineJson(_BaseTestCase):
@@ -335,8 +335,8 @@ class TestRebaselineExpectations(_BaseTestCase):
# FIXME: change this to use the test- ports.
calls = filter(lambda x: x != ['qmake', '-v'], self.tool.executive.calls)
- self.assertTrue(len(calls) == 1)
- self.assertTrue(len(calls[0]) == 26)
+ self.assertEqual(len(calls), 1)
+ self.assertEqual(len(calls[0]), 22)
def test_rebaseline_expectations_noop(self):
self._zero_out_test_expectations()
@@ -362,7 +362,7 @@ class TestRebaselineExpectations(_BaseTestCase):
'Bug(y) userscripts/test.html [ Crash ]\n')}
self._write('/userscripts/another-test.html', '')
- self.assertEqual(self.command._tests_to_rebaseline(self.lion_port), {'userscripts/another-test.html': set(['png', 'txt', 'wav'])})
+ self.assertDictEqual(self.command._tests_to_rebaseline(self.lion_port), {'userscripts/another-test.html': set(['png', 'txt', 'wav'])})
self.assertEqual(self._read(self.lion_expectations_path), '')
@@ -383,7 +383,7 @@ class TestAnalyzeBaselines(_BaseTestCase):
self.tool.port_factory.get = (lambda port_name=None, options=None: self.port)
self.lines = []
self.command._optimizer_class = _FakeOptimizer
- self.command._write = (lambda msg: self.lines.append(msg)) # pylint bug warning about unnecessary lambda? pylint: disable-msg=W0108
+ self.command._write = (lambda msg: self.lines.append(msg)) # pylint bug warning about unnecessary lambda? pylint: disable=W0108
def test_default(self):
self.command.execute(MockOptions(suffixes='txt', missing=False, platform=None), ['passes/text.html'], self.tool)
diff --git a/Tools/Scripts/webkitpy/tool/commands/roll.py b/Tools/Scripts/webkitpy/tool/commands/roll.py
deleted file mode 100644
index 37481b2b8..000000000
--- a/Tools/Scripts/webkitpy/tool/commands/roll.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright (c) 2011 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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.
-
-from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
-
-from webkitpy.tool import steps
-
-
-class RollChromiumDEPS(AbstractSequencedCommand):
- name = "roll-chromium-deps"
- help_text = "Updates Chromium DEPS (defaults to the last-known good revision of Chromium)"
- argument_names = "[CHROMIUM_REVISION]"
- steps = [
- steps.UpdateChromiumDEPS,
- steps.PrepareChangeLogForDEPSRoll,
- steps.ConfirmDiff,
- steps.Commit,
- ]
-
- def _prepare_state(self, options, args, tool):
- return {
- "chromium_revision": (args and args[0]),
- }
-
-
-class PostChromiumDEPSRoll(AbstractSequencedCommand):
- name = "post-chromium-deps-roll"
- help_text = "Posts a patch to update Chromium DEPS (revision defaults to the last-known good revision of Chromium)"
- argument_names = "CHROMIUM_REVISION CHROMIUM_REVISION_NAME"
- steps = [
- steps.CleanWorkingDirectory,
- steps.Update,
- steps.UpdateChromiumDEPS,
- steps.PrepareChangeLogForDEPSRoll,
- steps.CreateBug,
- steps.PostDiff,
- ]
-
- def _prepare_state(self, options, args, tool):
- options.review = False
- options.request_commit = True
-
- chromium_revision = args[0]
- chromium_revision_name = args[1]
- return {
- "chromium_revision": chromium_revision,
- "bug_title": "Roll Chromium DEPS to %s" % chromium_revision_name,
- "bug_description": "A DEPS roll a day keeps the build break away.",
- }
diff --git a/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py b/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py
deleted file mode 100644
index 1dae497bc..000000000
--- a/Tools/Scripts/webkitpy/tool/commands/roll_unittest.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright (C) 2011 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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.
-
-from webkitpy.thirdparty.mock import Mock
-from webkitpy.tool.commands.commandtest import CommandsTest
-from webkitpy.tool.commands.roll import *
-from webkitpy.tool.mocktool import MockOptions, MockTool
-
-
-class RollCommandsTest(CommandsTest):
- def test_update_chromium_deps(self):
- expected_logs = """Updating Chromium DEPS to 6764
-MOCK: MockDEPS.write_variable(chromium_rev, 6764)
-MOCK: user.open_url: file://...
-Was that diff correct?
-Committed r49824: <http://trac.webkit.org/changeset/49824>
-"""
- self.assert_execute_outputs(RollChromiumDEPS(), [6764], expected_logs=expected_logs)
-
- def test_update_chromium_deps_older_revision(self):
- options = MockOptions(non_interactive=False)
- expected_logs = """Current Chromium DEPS revision 6564 is newer than 5764.
-Unable to update Chromium DEPS
-"""
- self.assert_execute_outputs(RollChromiumDEPS(), [5764], options=options, expected_logs=expected_logs, expected_exception=SystemExit)
-
-
-class PostRollCommandsTest(CommandsTest):
- def test_prepare_state(self):
- postroll = PostChromiumDEPSRoll()
- options = MockOptions()
- tool = MockTool()
- lkgr_state = postroll._prepare_state(options, [None, "last-known good revision"], tool)
- self.assertEqual(None, lkgr_state["chromium_revision"])
- self.assertEqual("Roll Chromium DEPS to last-known good revision", lkgr_state["bug_title"])
- revision_state = postroll._prepare_state(options, ["1234", "r1234"], tool)
- self.assertEqual("1234", revision_state["chromium_revision"])
- self.assertEqual("Roll Chromium DEPS to r1234", revision_state["bug_title"])
diff --git a/Tools/Scripts/webkitpy/tool/commands/sheriffbot.py b/Tools/Scripts/webkitpy/tool/commands/sheriffbot.py
index 0f91be3ef..aea3b51bf 100644
--- a/Tools/Scripts/webkitpy/tool/commands/sheriffbot.py
+++ b/Tools/Scripts/webkitpy/tool/commands/sheriffbot.py
@@ -38,7 +38,7 @@ _log = logging.getLogger(__name__)
class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
- name = "sheriff-bot"
+ name = "webkitbot"
watchers = AbstractQueue.watchers + [
"abarth@webkit.org",
"eric@webkit.org",
@@ -49,7 +49,7 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
def begin_work_queue(self):
AbstractQueue.begin_work_queue(self)
self._sheriff = Sheriff(self._tool, self)
- self._irc_bot = IRCBot("sheriffbot", self._tool, self._sheriff, irc_commands)
+ self._irc_bot = IRCBot(self.name, self._tool, self._sheriff, irc_commands)
self._tool.ensure_irc_connected(self._irc_bot.irc_delegate())
def work_item_log_path(self, failure_map):
diff --git a/Tools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py b/Tools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py
index 9aa57b123..76caaf3c0 100644
--- a/Tools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py
@@ -26,8 +26,22 @@
# (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.commands.queuestest import QueuesTest
+from webkitpy.tool.commands.queuestest import QueuesTest, MockQueueEngine
+from webkitpy.tool.commands import SheriffBot
+from webkitpy.tool.mocktool import MockTool, MockOptions
+from webkitpy.tool.bot.irc_command import Rollout
class SheriffBotTest(QueuesTest):
- pass # No unittests as the moment.
+ def test_command_aliases(self):
+ tool = MockTool()
+ options = MockOptions()
+ options.ensure_value("confirm", False)
+ options.ensure_value("seconds_to_sleep", 120)
+ sheriffbot = SheriffBot()
+ sheriffbot.execute(options, [], tool, MockQueueEngine)
+ sheriffbot.begin_work_queue()
+ irc_bot = sheriffbot._irc_bot
+ # Test Rollout command aliases
+ revert_command, args = irc_bot._parse_command_and_args("revert")
+ self.assertEqual(revert_command, Rollout)
diff --git a/Tools/Scripts/webkitpy/tool/commands/suggestnominations.py b/Tools/Scripts/webkitpy/tool/commands/suggestnominations.py
index c197a1116..6244c295f 100644
--- a/Tools/Scripts/webkitpy/tool/commands/suggestnominations.py
+++ b/Tools/Scripts/webkitpy/tool/commands/suggestnominations.py
@@ -32,12 +32,119 @@ import re
from webkitpy.common.checkout.changelog import ChangeLogEntry
from webkitpy.common.config.committers import CommitterList
-from webkitpy.tool import steps
from webkitpy.tool.grammar import join_with_separators
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
-class SuggestNominations(AbstractDeclarativeCommand):
+class CommitLogError(Exception):
+ def __init__(self):
+ Exception.__init__(self)
+
+
+class CommitLogMissingReviewer(CommitLogError):
+ def __init__(self):
+ CommitLogError.__init__(self)
+
+
+class AbstractCommitLogCommand(Command):
+ _leading_indent_regexp = re.compile(r"^[ ]{4}", re.MULTILINE)
+ _reviewed_by_regexp = re.compile(ChangeLogEntry.reviewed_by_regexp, re.MULTILINE)
+ _patch_by_regexp = re.compile(r'^Patch by (?P<name>.+?)\s+<(?P<email>[^<>]+)> on (?P<date>\d{4}-\d{2}-\d{2})$', re.MULTILINE)
+ _committer_regexp = re.compile(r'^Author: (?P<email>\S+)\s+<[^>]+>$', re.MULTILINE)
+ _date_regexp = re.compile(r'^Date: (?P<date>\d{4}-\d{2}-\d{2}) (?P<time>\d{2}:\d{2}:\d{2}) [\+\-]\d{4}$', re.MULTILINE)
+ _revision_regexp = re.compile(r'^git-svn-id: http://svn.webkit.org/repository/webkit/trunk@(?P<svnid>\d+) (?P<gitid>[0-9a-f\-]{36})$', re.MULTILINE)
+
+ def __init__(self, options=None):
+ options = options or []
+ options += [
+ make_option("--max-commit-age", action="store", dest="max_commit_age", type="int", default=9, help="Specify maximum commit age to consider (in months)."),
+ ]
+ options = sorted(options, cmp=lambda a, b: cmp(a._long_opts, b._long_opts))
+ super(AbstractCommitLogCommand, self).__init__(options=options)
+ # FIXME: This should probably be on the tool somewhere.
+ self._committer_list = CommitterList()
+
+ def _init_options(self, options):
+ self.verbose = options.verbose
+ self.max_commit_age = options.max_commit_age
+
+ # FIXME: This should move to scm.py
+ def _recent_commit_messages(self):
+ git_log = self._tool.executive.run_command(['git', 'log', '--date=iso', '--since="%s months ago"' % self.max_commit_age])
+ messages = re.compile(r"^commit \w{40}$", re.MULTILINE).split(git_log)[1:] # Ignore the first message which will be empty.
+ for message in messages:
+ # Unindent all the lines
+ (message, _) = self._leading_indent_regexp.subn("", message)
+ yield message.lstrip() # Remove any leading newlines from the log message.
+
+ def _author_name_from_email(self, email):
+ contributor = self._committer_list.contributor_by_email(email)
+ return contributor.full_name if contributor else None
+
+ def _contributor_from_email(self, email):
+ contributor = self._committer_list.contributor_by_email(email)
+ return contributor if contributor else None
+
+ def _parse_commit_message(self, commit_message):
+ committer_match = self._committer_regexp.search(commit_message)
+ if not committer_match:
+ raise CommitLogError
+
+ committer_email = committer_match.group('email')
+ if not committer_email:
+ raise CommitLogError
+
+ committer = self._contributor_from_email(committer_email)
+ if not committer:
+ raise CommitLogError
+
+ commit_date_match = self._date_regexp.search(commit_message)
+ if not commit_date_match:
+ raise CommitLogError
+ commit_date = commit_date_match.group('date')
+
+ revision_match = self._revision_regexp.search(commit_message)
+ if not revision_match:
+ raise CommitLogError
+ revision = revision_match.group('svnid')
+
+ # Look for "Patch by" line first, which is used for non-committer contributors;
+ # otherwise, use committer info determined above.
+ author_match = self._patch_by_regexp.search(commit_message)
+ if not author_match:
+ author_match = committer_match
+
+ author_email = author_match.group('email')
+ if not author_email:
+ author_email = committer_email
+
+ author_name = author_match.group('name') if 'name' in author_match.groupdict() else None
+ if not author_name:
+ author_name = self._author_name_from_email(author_email)
+ if not author_name:
+ raise CommitLogError
+
+ contributor = self._contributor_from_email(author_email)
+ if contributor and author_name != contributor.full_name and contributor.full_name:
+ author_name = contributor.full_name
+
+ reviewer_match = self._reviewed_by_regexp.search(commit_message)
+ if not reviewer_match:
+ raise CommitLogMissingReviewer
+ reviewers = reviewer_match.group('reviewer')
+
+ return {
+ 'committer': committer,
+ 'commit_date': commit_date,
+ 'revision': revision,
+ 'author_email': author_email,
+ 'author_name': author_name,
+ 'contributor': contributor,
+ 'reviewers': reviewers,
+ }
+
+
+class SuggestNominations(AbstractCommitLogCommand):
name = "suggest-nominations"
help_text = "Suggest contributors for committer/reviewer nominations"
@@ -45,118 +152,70 @@ class SuggestNominations(AbstractDeclarativeCommand):
options = [
make_option("--committer-minimum", action="store", dest="committer_minimum", type="int", default=10, help="Specify minimum patch count for Committer nominations."),
make_option("--reviewer-minimum", action="store", dest="reviewer_minimum", type="int", default=80, help="Specify minimum patch count for Reviewer nominations."),
- make_option("--max-commit-age", action="store", dest="max_commit_age", type="int", default=9, help="Specify max commit age to consider for nominations (in months)."),
make_option("--show-commits", action="store_true", dest="show_commits", default=False, help="Show commit history with nomination suggestions."),
]
-
- AbstractDeclarativeCommand.__init__(self, options=options)
- # FIXME: This should probably be on the tool somewhere.
- self._committer_list = CommitterList()
-
- _counters_by_name = {}
- _counters_by_email = {}
+ super(SuggestNominations, self).__init__(options=options)
def _init_options(self, options):
+ super(SuggestNominations, self)._init_options(options)
self.committer_minimum = options.committer_minimum
self.reviewer_minimum = options.reviewer_minimum
- self.max_commit_age = options.max_commit_age
self.show_commits = options.show_commits
- self.verbose = options.verbose
- # FIXME: This should move to scm.py
- def _recent_commit_messages(self):
- git_log = self._tool.executive.run_command(['git', 'log', '--since="%s months ago"' % self.max_commit_age])
- match_git_svn_id = re.compile(r"\n\n git-svn-id:.*\n", re.MULTILINE)
- match_get_log_lines = re.compile(r"^\S.*\n", re.MULTILINE)
- match_leading_indent = re.compile(r"^[ ]{4}", re.MULTILINE)
+ def _count_commit(self, commit, analysis):
+ author_name = commit['author_name']
+ author_email = commit['author_email']
+ revision = commit['revision']
+ commit_date = commit['commit_date']
+
+ # See if we already have a contributor with this author_name or email
+ counter_by_name = analysis['counters_by_name'].get(author_name)
+ counter_by_email = analysis['counters_by_email'].get(author_email)
+ if counter_by_name:
+ if counter_by_email:
+ if counter_by_name != counter_by_email:
+ # Merge these two counters This is for the case where we had
+ # John Smith (jsmith@gmail.com) and Jonathan Smith (jsmith@apple.com)
+ # and just found a John Smith (jsmith@apple.com). Now we know the
+ # two names are the same person
+ counter_by_name['names'] |= counter_by_email['names']
+ counter_by_name['emails'] |= counter_by_email['emails']
+ counter_by_name['count'] += counter_by_email.get('count', 0)
+ analysis['counters_by_email'][author_email] = counter_by_name
+ else:
+ # Add email to the existing counter
+ analysis['counters_by_email'][author_email] = counter_by_name
+ counter_by_name['emails'] |= set([author_email])
+ else:
+ if counter_by_email:
+ # Add name to the existing counter
+ analysis['counters_by_name'][author_name] = counter_by_email
+ counter_by_email['names'] |= set([author_name])
+ else:
+ # Create new counter
+ new_counter = {'names': set([author_name]), 'emails': set([author_email]), 'latest_name': author_name, 'latest_email': author_email, 'commits': ""}
+ analysis['counters_by_name'][author_name] = new_counter
+ analysis['counters_by_email'][author_email] = new_counter
- messages = re.split(r"commit \w{40}", git_log)[1:] # Ignore the first message which will be empty.
- for message in messages:
- # Remove any lines from git and unindent all the lines
- (message, _) = match_git_svn_id.subn("", message)
- (message, _) = match_get_log_lines.subn("", message)
- (message, _) = match_leading_indent.subn("", message)
- yield message.lstrip() # Remove any leading newlines from the log message.
+ assert(analysis['counters_by_name'][author_name] == analysis['counters_by_email'][author_email])
+ counter = analysis['counters_by_name'][author_name]
+ counter['count'] = counter.get('count', 0) + 1
- # e.g. Patch by Eric Seidel <eric@webkit.org> on 2011-09-15
- patch_by_regexp = r'^Patch by (?P<name>.+?)\s+<(?P<email>[^<>]+)> on (?P<date>\d{4}-\d{2}-\d{2})$'
+ if revision.isdigit():
+ revision = "http://trac.webkit.org/changeset/" + revision
+ counter['commits'] += " commit: %s on %s by %s (%s)\n" % (revision, commit_date, author_name, author_email)
def _count_recent_patches(self):
- # This entire block could be written as a map/reduce over the messages.
- for message in self._recent_commit_messages():
- # FIXME: This should use ChangeLogEntry to do the entire parse instead
- # of grabbing at its regexps.
- dateline_match = re.match(ChangeLogEntry.date_line_regexp, message, re.MULTILINE)
- if not dateline_match:
- # Modern commit messages don't just dump the ChangeLog entry, but rather
- # have a special Patch by line for non-committers.
- dateline_match = re.search(self.patch_by_regexp, message, re.MULTILINE)
- if not dateline_match:
- continue
-
- author_email = dateline_match.group("email")
- if not author_email:
- continue
-
- # We only care about reviewed patches, so make sure it has a valid reviewer line.
- reviewer_match = re.search(ChangeLogEntry.reviewed_by_regexp, message, re.MULTILINE)
- # We might also want to validate the reviewer name against the committer list.
- if not reviewer_match or not reviewer_match.group("reviewer"):
- continue
-
- author_name = dateline_match.group("name")
- if not author_name:
+ analysis = {
+ 'counters_by_name': {},
+ 'counters_by_email': {},
+ }
+ for commit_message in self._recent_commit_messages():
+ try:
+ self._count_commit(self._parse_commit_message(commit_message), analysis)
+ except CommitLogError, exception:
continue
-
- if re.search("([^a-zA-Z]and[^a-zA-Z])|(,)|(@)", author_name):
- # This entry seems to have multiple reviewers, or invalid characters, so reject it.
- continue
-
- svn_id_match = re.search(ChangeLogEntry.svn_id_regexp, message, re.MULTILINE)
- if svn_id_match:
- svn_id = svn_id_match.group("svnid")
- if not svn_id_match or not svn_id:
- svn_id = "unknown"
- commit_date = dateline_match.group("date")
-
- # See if we already have a contributor with this name or email
- counter_by_name = self._counters_by_name.get(author_name)
- counter_by_email = self._counters_by_email.get(author_email)
- if counter_by_name:
- if counter_by_email:
- if counter_by_name != counter_by_email:
- # Merge these two counters This is for the case where we had
- # John Smith (jsmith@gmail.com) and Jonathan Smith (jsmith@apple.com)
- # and just found a John Smith (jsmith@apple.com). Now we know the
- # two names are the same person
- counter_by_name['names'] |= counter_by_email['names']
- counter_by_name['emails'] |= counter_by_email['emails']
- counter_by_name['count'] += counter_by_email.get('count', 0)
- self._counters_by_email[author_email] = counter_by_name
- else:
- # Add email to the existing counter
- self._counters_by_email[author_email] = counter_by_name
- counter_by_name['emails'] |= set([author_email])
- else:
- if counter_by_email:
- # Add name to the existing counter
- self._counters_by_name[author_name] = counter_by_email
- counter_by_email['names'] |= set([author_name])
- else:
- # Create new counter
- new_counter = {'names': set([author_name]), 'emails': set([author_email]), 'latest_name': author_name, 'latest_email': author_email, 'commits': ""}
- self._counters_by_name[author_name] = new_counter
- self._counters_by_email[author_email] = new_counter
-
- assert(self._counters_by_name[author_name] == self._counters_by_email[author_email])
- counter = self._counters_by_name[author_name]
- counter['count'] = counter.get('count', 0) + 1
-
- if svn_id.isdigit():
- svn_id = "http://trac.webkit.org/changeset/" + svn_id
- counter['commits'] += " commit: %s on %s by %s (%s)\n" % (svn_id, commit_date, author_name, author_email)
-
- return self._counters_by_email
+ return analysis['counters_by_email']
def _collect_nominations(self, counters_by_email):
nominations = []
@@ -172,7 +231,7 @@ class SuggestNominations(AbstractDeclarativeCommand):
if patch_count >= self.committer_minimum and (not contributor or not contributor.can_commit):
roles.append("committer")
- if patch_count >= self.reviewer_minimum and (not contributor or not contributor.can_review):
+ if patch_count >= self.reviewer_minimum and contributor and contributor.can_commit and not contributor.can_review:
roles.append("reviewer")
if roles:
nominations.append({
@@ -183,7 +242,7 @@ class SuggestNominations(AbstractDeclarativeCommand):
})
return nominations
- def _print_nominations(self, nominations):
+ def _print_nominations(self, nominations, counters_by_email):
def nomination_cmp(a_nomination, b_nomination):
roles_result = cmp(a_nomination['roles'], b_nomination['roles'])
if roles_result:
@@ -197,7 +256,7 @@ class SuggestNominations(AbstractDeclarativeCommand):
# This is a little bit of a hack, but its convienent to just pass the nomination dictionary to the formating operator.
nomination['roles_string'] = join_with_separators(nomination['roles']).upper()
print "%(roles_string)s: %(author_name)s (%(author_email)s) has %(patch_count)s reviewed patches" % nomination
- counter = self._counters_by_email[nomination['author_email']]
+ counter = counters_by_email[nomination['author_email']]
if self.show_commits:
print counter['commits']
@@ -238,10 +297,9 @@ class SuggestNominations(AbstractDeclarativeCommand):
self._init_options(options)
patch_counts = self._count_recent_patches()
nominations = self._collect_nominations(patch_counts)
- self._print_nominations(nominations)
+ self._print_nominations(nominations, patch_counts)
if self.verbose:
self._print_counts(patch_counts)
-
if __name__ == "__main__":
SuggestNominations()
diff --git a/Tools/Scripts/webkitpy/tool/commands/suggestnominations_unittest.py b/Tools/Scripts/webkitpy/tool/commands/suggestnominations_unittest.py
index 88be25303..63054c516 100644
--- a/Tools/Scripts/webkitpy/tool/commands/suggestnominations_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/commands/suggestnominations_unittest.py
@@ -36,7 +36,7 @@ class SuggestNominationsTest(CommandsTest):
mock_git_output = """commit 60831dde5beb22f35aef305a87fca7b5f284c698
Author: fpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
-Date: Thu Sep 15 19:56:21 2011 +0000
+Date: 2011-09-15 19:56:21 +0000
Value profiles collect no information for global variables
https://bugs.webkit.org/show_bug.cgi?id=68143
@@ -45,21 +45,43 @@ Date: Thu Sep 15 19:56:21 2011 +0000
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@95219 268f45cc-cd09-0410-ab3c-d52691b4dbfc
"""
- mock_same_author_commit_message = """Value profiles collect no information for global variables
+ mock_same_author_commit_message = """Author: fpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
+Date: 2011-09-15 19:56:21 +0000
+
+Value profiles collect no information for global variables
https://bugs.webkit.org/show_bug.cgi?id=68143
-Reviewed by Geoffrey Garen."""
+Reviewed by Geoffrey Garen.
+
+git-svn-id: http://svn.webkit.org/repository/webkit/trunk@95219 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+"""
+
+ def _make_options(self, **kwargs):
+ defaults = {
+ 'committer_minimum': 10,
+ 'max_commit_age': 9,
+ 'reviewer_minimum': 80,
+ 'show_commits': False,
+ 'verbose': False,
+ }
+ options = MockOptions(**defaults)
+ options.update(**kwargs)
+ return options
def test_recent_commit_messages(self):
tool = MockTool()
suggest_nominations = SuggestNominations()
- suggest_nominations._init_options(options=MockOptions(reviewer_minimum=80, committer_minimum=10, max_commit_age=9, show_commits=False, verbose=False))
+ suggest_nominations._init_options(options=self._make_options())
suggest_nominations.bind_to_tool(tool)
tool.executive.run_command = lambda command: self.mock_git_output
self.assertEqual(list(suggest_nominations._recent_commit_messages()), [self.mock_same_author_commit_message])
- mock_non_committer_commit_message = """Let TestWebKitAPI work for chromium
+ mock_non_committer_commit_message = """
+Author: commit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
+Date: 2009-09-15 14:08:42 +0000
+
+Let TestWebKitAPI work for chromium
https://bugs.webkit.org/show_bug.cgi?id=67756
Patch by Xianzhu Wang <wangxianzhu@chromium.org> on 2011-09-15
@@ -67,11 +89,15 @@ Reviewed by Sam Weinig.
Source/WebKit/chromium:
-* WebKit.gyp:"""
+* WebKit.gyp:
+
+git-svn-id: http://svn.webkit.org/repository/webkit/trunk@95188 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+"""
def test_basic(self):
expected_stdout = "REVIEWER: Xianzhu Wang (wangxianzhu@chromium.org) has 88 reviewed patches\n"
+ options = self._make_options()
suggest_nominations = SuggestNominations()
- suggest_nominations._init_options(options=MockOptions(reviewer_minimum=80, committer_minimum=10, max_commit_age=9, show_commits=False, verbose=False))
+ suggest_nominations._init_options(options=options)
suggest_nominations._recent_commit_messages = lambda: [self.mock_non_committer_commit_message for _ in range(88)]
- self.assert_execute_outputs(suggest_nominations, [], expected_stdout=expected_stdout, options=MockOptions(reviewer_minimum=80, committer_minimum=10, max_commit_age=9, show_commits=False, verbose=False))
+ self.assert_execute_outputs(suggest_nominations, [], expected_stdout=expected_stdout, options=options)
diff --git a/Tools/Scripts/webkitpy/tool/commands/upload.py b/Tools/Scripts/webkitpy/tool/commands/upload.py
index 5cd0de9e0..69dc4f715 100644
--- a/Tools/Scripts/webkitpy/tool/commands/upload.py
+++ b/Tools/Scripts/webkitpy/tool/commands/upload.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (c) 2009, 2010 Google Inc. All rights reserved.
# Copyright (c) 2009 Apple Inc. All rights reserved.
#
@@ -44,12 +43,12 @@ from webkitpy.thirdparty.mock import Mock
from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
from webkitpy.tool.comments import bug_comment_from_svn_revision
from webkitpy.tool.grammar import pluralize, join_with_separators
-from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+from webkitpy.tool.multicommandtool import Command
_log = logging.getLogger(__name__)
-class CommitMessageForCurrentDiff(AbstractDeclarativeCommand):
+class CommitMessageForCurrentDiff(Command):
name = "commit-message"
help_text = "Print a commit message suitable for the uncommitted changes"
@@ -57,7 +56,7 @@ class CommitMessageForCurrentDiff(AbstractDeclarativeCommand):
options = [
steps.Options.git_commit,
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
def execute(self, options, args, tool):
# This command is a useful test to make sure commit_message_for_this_commit
@@ -65,7 +64,7 @@ class CommitMessageForCurrentDiff(AbstractDeclarativeCommand):
print "%s" % tool.checkout().commit_message_for_this_commit(options.git_commit).message()
-class CleanPendingCommit(AbstractDeclarativeCommand):
+class CleanPendingCommit(Command):
name = "clean-pending-commit"
help_text = "Clear r+ on obsolete patches so they do not appear in the pending-commit list."
@@ -95,7 +94,7 @@ class CleanPendingCommit(AbstractDeclarativeCommand):
# FIXME: This should be share more logic with AssignToCommitter and CleanPendingCommit
-class CleanReviewQueue(AbstractDeclarativeCommand):
+class CleanReviewQueue(Command):
name = "clean-review-queue"
help_text = "Clear r? on obsolete patches so they do not appear in the pending-review list."
@@ -120,7 +119,7 @@ class CleanReviewQueue(AbstractDeclarativeCommand):
self._tool.bugs.obsolete_attachment(patch.id(), message)
-class AssignToCommitter(AbstractDeclarativeCommand):
+class AssignToCommitter(Command):
name = "assign-to-committer"
help_text = "Assign bug to whoever attached the most recent r+'d patch"
@@ -242,6 +241,15 @@ class LandSafely(AbstractPatchUploadingCommand):
]
+class HasLanded(AbstractPatchUploadingCommand):
+ name = "has-landed"
+ help_text = "Check that the current code was successfully landed and no changes remain."
+ argument_names = "[BUGID]"
+ steps = [
+ steps.HasLanded,
+ ]
+
+
class Prepare(AbstractSequencedCommand):
name = "prepare"
help_text = "Creates a bug (or prompts for an existing bug) and prepares the ChangeLogs"
@@ -298,7 +306,7 @@ class EditChangeLogs(AbstractSequencedCommand):
]
-class PostCommits(AbstractDeclarativeCommand):
+class PostCommits(Command):
name = "post-commits"
help_text = "Attach a range of local commits to bugs as patch files"
argument_names = "COMMITISH"
@@ -312,7 +320,7 @@ class PostCommits(AbstractDeclarativeCommand):
steps.Options.review,
steps.Options.request_commit,
]
- AbstractDeclarativeCommand.__init__(self, options=options, requires_local_commits=True)
+ Command.__init__(self, options=options, requires_local_commits=True)
def _comment_text_for_commit(self, options, commit_message, tool, commit_id):
comment_text = None
@@ -350,7 +358,7 @@ class PostCommits(AbstractDeclarativeCommand):
# FIXME: This command needs to be brought into the modern age with steps and CommitInfo.
-class MarkBugFixed(AbstractDeclarativeCommand):
+class MarkBugFixed(Command):
name = "mark-bug-fixed"
help_text = "Mark the specified bug as fixed"
argument_names = "[SVN_REVISION]"
@@ -361,7 +369,7 @@ class MarkBugFixed(AbstractDeclarativeCommand):
make_option("--open", action="store_true", default=False, dest="open_bug", help="Open bug in default web browser (Mac only)."),
make_option("--update-only", action="store_true", default=False, dest="update_only", help="Add comment to the bug, but do not close it."),
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
# FIXME: We should be using checkout().changelog_entries_for_revision(...) instead here.
def _fetch_commit_log(self, tool, svn_revision):
@@ -431,7 +439,7 @@ class MarkBugFixed(AbstractDeclarativeCommand):
# FIXME: Requires unit test. Blocking issue: too complex for now.
-class CreateBug(AbstractDeclarativeCommand):
+class CreateBug(Command):
name = "create-bug"
help_text = "Create a bug from local changes or local commits"
argument_names = "[COMMITISH]"
@@ -444,7 +452,7 @@ class CreateBug(AbstractDeclarativeCommand):
make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review."),
make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review."),
]
- AbstractDeclarativeCommand.__init__(self, options=options)
+ Command.__init__(self, options=options)
def create_bug_from_commit(self, options, args, tool):
commit_ids = tool.scm().commit_ids_from_commitish_arguments(args)
diff --git a/Tools/Scripts/webkitpy/tool/comments.py b/Tools/Scripts/webkitpy/tool/comments.py
index 771953e69..771953e69 100755..100644
--- a/Tools/Scripts/webkitpy/tool/comments.py
+++ b/Tools/Scripts/webkitpy/tool/comments.py
diff --git a/Tools/Scripts/webkitpy/tool/gcovr b/Tools/Scripts/webkitpy/tool/gcovr
new file mode 100755
index 000000000..e4a1f8850
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/gcovr
@@ -0,0 +1,1029 @@
+#! /usr/bin/env python
+#
+# A report generator for gcov 3.4
+#
+# This routine generates a format that is similar to the format generated
+# by the Python coverage.py module. This code is similar to the
+# data processing performed by lcov's geninfo command. However, we
+# don't worry about parsing the *.gcna files, and backwards compatibility for
+# older versions of gcov is not supported.
+#
+# Outstanding issues
+# - verify that gcov 3.4 or newer is being used
+# - verify support for symbolic links
+#
+# gcovr is a FAST project. For documentation, bug reporting, and
+# updates, see https://software.sandia.gov/trac/fast/wiki/gcovr
+#
+# _________________________________________________________________________
+#
+# FAST: Utilities for Agile Software Development
+# Copyright (c) 2008 Sandia Corporation.
+# This software is distributed under the BSD License.
+# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
+# the U.S. Government retains certain rights in this software.
+# For more information, see the FAST README.txt file.
+#
+# $Revision: 2839 $
+# $Date: 2013-05-27 11:13:17 -0700 (Mon, 27 May 2013) $
+# _________________________________________________________________________
+#
+
+import copy
+import glob
+import os
+import re
+import subprocess
+import sys
+import time
+import xml.dom.minidom
+
+from optparse import OptionParser
+from string import Template
+from os.path import normpath
+
+__version__ = "2.5-prerelease"
+src_revision = "$Revision: 2839 $"
+gcov_cmd = "gcov"
+
+output_re = re.compile("[Cc]reating [`'](.*)'$")
+source_re = re.compile("cannot open (source|graph) file")
+
+starting_dir = os.getcwd()
+
+
+def version_str():
+ ans = __version__
+ m = re.match('\$Revision:\s*(\S+)\s*\$', src_revision)
+ if m:
+ ans = ans + " (r%s)" % (m.group(1))
+ return ans
+
+#
+# Container object for coverage statistics
+#
+class CoverageData(object):
+
+ def __init__(self, fname, uncovered, uncovered_exceptional, covered, branches, noncode):
+ self.fname=fname
+ # Shallow copies are cheap & "safe" because the caller will
+ # throw away their copies of covered & uncovered after calling
+ # us exactly *once*
+ self.uncovered = copy.copy(uncovered)
+ self.uncovered_exceptional = copy.copy(uncovered_exceptional)
+ self.covered = copy.copy(covered)
+ self.noncode = copy.copy(noncode)
+ # But, a deep copy is required here
+ self.all_lines = copy.deepcopy(uncovered)
+ self.all_lines.update(uncovered_exceptional)
+ self.all_lines.update(covered.keys())
+ self.branches = copy.deepcopy(branches)
+
+ def update(self, uncovered, uncovered_exceptional, covered, branches, noncode):
+ self.all_lines.update(uncovered)
+ self.all_lines.update(uncovered_exceptional)
+ self.all_lines.update(covered.keys())
+ self.uncovered.update(uncovered)
+ self.uncovered_exceptional.update(uncovered_exceptional)
+ self.noncode.intersection_update(noncode)
+ for k in covered.keys():
+ self.covered[k] = self.covered.get(k,0) + covered[k]
+ for k in branches.keys():
+ for b in branches[k]:
+ d = self.branches.setdefault(k, {})
+ d[b] = d.get(b, 0) + branches[k][b]
+ self.uncovered.difference_update(self.covered.keys())
+ self.uncovered_exceptional.difference_update(self.covered.keys())
+
+ def uncovered_str(self, exceptional):
+ if options.show_branch:
+ # Don't do any aggregation on branch results
+ tmp = []
+ for line in self.branches.keys():
+ for branch in self.branches[line]:
+ if self.branches[line][branch] == 0:
+ tmp.append(line)
+ break
+
+ tmp.sort()
+ return ",".join([str(x) for x in tmp]) or ""
+
+ if exceptional:
+ tmp = list(self.uncovered_exceptional)
+ else:
+ tmp = list(self.uncovered)
+ if len(tmp) == 0:
+ return ""
+
+ tmp.sort()
+ first = None
+ last = None
+ ranges=[]
+ for item in tmp:
+ if last is None:
+ first=item
+ last=item
+ elif item == (last+1):
+ last=item
+ else:
+ if len(self.noncode.intersection(range(last+1,item))) \
+ == item - last - 1:
+ last = item
+ continue
+
+ if first==last:
+ ranges.append(str(first))
+ else:
+ ranges.append(str(first)+"-"+str(last))
+ first=item
+ last=item
+ if first==last:
+ ranges.append(str(first))
+ else:
+ ranges.append(str(first)+"-"+str(last))
+ return ",".join(ranges)
+
+ def coverage(self):
+ if ( options.show_branch ):
+ total = 0
+ cover = 0
+ for line in self.branches.keys():
+ for branch in self.branches[line].keys():
+ total += 1
+ cover += self.branches[line][branch] > 0 and 1 or 0
+ else:
+ total = len(self.all_lines)
+ cover = len(self.covered)
+
+ percent = total and str(int(100.0*cover/total)) or "--"
+ return (total, cover, percent)
+
+ def summary(self):
+ tmp = options.filter.sub('',self.fname)
+ if not self.fname.endswith(tmp):
+ # Do no truncation if the filter does not start matching at
+ # the beginning of the string
+ tmp = self.fname
+ tmp = tmp.ljust(40)
+ if len(tmp) > 40:
+ tmp=tmp+"\n"+" "*40
+
+ (total, cover, percent) = self.coverage()
+ uncovered_lines = self.uncovered_str(False)
+ if not options.show_branch:
+ t = self.uncovered_str(True)
+ if len(t):
+ uncovered_lines += " [* " + t + "]";
+ return ( total, cover,
+ tmp + str(total).rjust(8) + str(cover).rjust(8) + \
+ percent.rjust(6) + "% " + uncovered_lines )
+
+
+def resolve_symlinks(orig_path):
+ """
+ Return the normalized absolute path name with all symbolic links resolved
+ """
+ drive,tmp = os.path.splitdrive(os.path.abspath(orig_path))
+ if not drive:
+ drive = os.path.sep
+ parts = tmp.split(os.path.sep)
+ actual_path = [drive]
+ while parts:
+ actual_path.append(parts.pop(0))
+ if not os.path.islink(os.path.join(*actual_path)):
+ continue
+ actual_path[-1] = os.readlink(os.path.join(*actual_path))
+ tmp_drive, tmp_path = os.path.splitdrive(
+ resolve_symlinks(os.path.join(*actual_path)) )
+ if tmp_drive:
+ drive = tmp_drive
+ actual_path = [drive] + tmp_path.split(os.path.sep)
+ return os.path.join(*actual_path)
+
+
+def path_startswith(path, base):
+ return path.startswith(base) and (
+ len(base) == len(path) or path[len(base)] == os.path.sep )
+
+
+class PathAliaser(object):
+ def __init__(self):
+ self.aliases = {}
+ self.master_targets = set()
+ self.preferred_name = {}
+
+ def master_path(self, path):
+ match_found = False
+ while True:
+ for base, alias in self.aliases.items():
+ if path_startswith(path, base):
+ path = alias + path[len(base):]
+ match_found = True
+ break
+ for master_base in self.master_targets:
+ if path_startswith(path, master_base):
+ return path, master_base, True
+ if match_found:
+ sys.stderr.write(
+ "(ERROR) violating fundamental assumption while walking "
+ "directory tree.\n\tPlease report this to the gcovr "
+ "developers.\n" )
+ return path, None, match_found
+
+ def unalias_path(self, path):
+ path = resolve_symlinks(path)
+ path, master_base, known_path = self.master_path(path)
+ if not known_path:
+ return path
+ # Try and resolve the preferred name for this location
+ if master_base in self.preferred_name:
+ return self.preferred_name[master_base] + path[len(master_base):]
+ return path
+
+ def add_master_target(self, master):
+ self.master_targets.add(master)
+
+ def add_alias(self, target, master):
+ self.aliases[target] = master
+
+ def set_preferred(self, master, preferred):
+ self.preferred_name[master] = preferred
+
+aliases = PathAliaser()
+
+# This is UGLY. Here's why: UNIX resolves symbolic links by walking the
+# entire directory structure. What that means is that relative links
+# are always relative to the actual directory inode, and not the
+# "virtual" path that the user might have traversed (over symlinks) on
+# the way to that directory. Here's the canonical example:
+#
+# a / b / c / testfile
+# a / d / e --> ../../a/b
+# m / n --> /a
+# x / y / z --> /m/n/d
+#
+# If we start in "y", we will see the following directory structure:
+# y
+# |-- z
+# |-- e
+# |-- c
+# |-- testfile
+#
+# The problem is that using a simple traversal based on the Python
+# documentation:
+#
+# (os.path.join(os.path.dirname(path), os.readlink(result)))
+#
+# will not work: we will see a link to /m/n/d from /x/y, but completely
+# miss the fact that n is itself a link. If we then naively attempt to
+# apply the "c" relative link, we get an intermediate path that looks
+# like "/m/n/d/e/../../a/b", which would get normalized to "/m/n/a/b"; a
+# nonexistant path. The solution is that we need to walk the original
+# path, along with the full path of all links 1 directory at a time and
+# check for embedded symlinks.
+#
+def link_walker(path):
+ targets = [os.path.abspath(path)]
+ while targets:
+ target_dir = targets.pop(0)
+ actual_dir = resolve_symlinks(target_dir)
+ #print "target dir: %s (%s)" % (target_dir, actual_dir)
+ master_name, master_base, visited = aliases.master_path(actual_dir)
+ if visited:
+ #print " ...root already visited as %s" % master_name
+ aliases.add_alias(target_dir, master_name)
+ continue
+ if master_name != target_dir:
+ aliases.set_preferred(master_name, target_dir)
+ aliases.add_alias(target_dir, master_name)
+ aliases.add_master_target(master_name)
+ #print " ...master name = %s" % master_name
+ #print " ...walking %s" % target_dir
+ for root, dirs, files in os.walk(target_dir, topdown=True):
+ #print " ...reading %s" % root
+ for d in dirs:
+ tmp = os.path.abspath(os.path.join(root, d))
+ #print " ...checking %s" % tmp
+ if os.path.islink(tmp):
+ #print " ...buffering link %s" % tmp
+ targets.append(tmp)
+ yield root, dirs, files
+
+
+def search_file(expr, path):
+ """
+ Given a search path, recursively descend to find files that match a
+ regular expression.
+ """
+ ans = []
+ pattern = re.compile(expr)
+ if path is None or path == ".":
+ path = os.getcwd()
+ elif not os.path.exists(path):
+ raise IOError("Unknown directory '"+path+"'")
+ for root, dirs, files in link_walker(path):
+ for name in files:
+ if pattern.match(name):
+ name = os.path.join(root,name)
+ if os.path.islink(name):
+ ans.append( os.path.abspath(os.readlink(name)) )
+ else:
+ ans.append( os.path.abspath(name) )
+ return ans
+
+
+#
+# Get the list of datafiles in the directories specified by the user
+#
+def get_datafiles(flist, options):
+ allfiles=[]
+ for dir in flist:
+ if options.verbose:
+ sys.stdout.write( "Scanning directory %s for gcda/gcno files...\n"
+ % (dir, ) )
+ files = search_file(".*\.gc(da|no)$", dir)
+ # gcno files will *only* produce uncovered results; however,
+ # that is useful information for the case where a compilation
+ # unit is never actually exercised by the test code. So, we
+ # will process gcno files, but ONLY if there is no corresponding
+ # gcda file.
+ gcda_files = [file for file in files if file.endswith('gcda')]
+ tmp = set(gcda_files)
+ gcno_files = [ file for file in files if
+ file.endswith('gcno') and file[:-2]+'da' not in tmp ]
+ if options.verbose:
+ sys.stdout.write(
+ "Found %d files (and will process %d)\n" %
+ ( len(files), len(gcda_files) + len(gcno_files) ) )
+ allfiles.extend(gcda_files)
+ allfiles.extend(gcno_files)
+ return allfiles
+
+
+def process_gcov_data(file, covdata, options):
+ INPUT = open(file,"r")
+ #
+ # Get the filename
+ #
+ line = INPUT.readline()
+ segments=line.split(':',3)
+ if len(segments) != 4 or not segments[2].lower().strip().endswith('source'):
+ raise RuntimeError('Fatal error parsing gcov file, line 1: \n\t"%s"' % line.rstrip())
+ currdir = os.getcwd()
+ os.chdir(starting_dir)
+ fname = aliases.unalias_path(os.path.abspath((segments[-1]).strip()))
+ os.chdir(currdir)
+ if options.verbose:
+ sys.stdout.write("Parsing coverage data for file %s\n" % fname)
+ #
+ # Return if the filename does not match the filter
+ #
+ if not options.filter.match(fname):
+ if options.verbose:
+ sys.stdout.write(" Filtering coverage data for file %s\n" % fname)
+ return
+ #
+ # Return if the filename matches the exclude pattern
+ #
+ for i in range(0,len(options.exclude)):
+ if options.exclude[i].match(options.filter.sub('',fname)) or \
+ options.exclude[i].match(fname) or \
+ options.exclude[i].match(os.path.abspath(fname)):
+ if options.verbose:
+ sys.stdout.write(" Excluding coverage data for file %s\n" % fname)
+ return
+ #
+ # Parse each line, and record the lines
+ # that are uncovered
+ #
+ noncode = set()
+ uncovered = set()
+ uncovered_exceptional = set()
+ covered = {}
+ branches = {}
+ #first_record=True
+ lineno = 0
+ for line in INPUT:
+ segments=line.split(":",2)
+ #print "HERE", segments
+ tmp = segments[0].strip()
+ if len(segments) > 1:
+ try:
+ lineno = int(segments[1].strip())
+ except:
+ pass # keep previous line number!
+
+ if tmp[0] == '#':
+ uncovered.add( lineno )
+ elif tmp[0] == '=':
+ uncovered_exceptional.add( lineno )
+ elif tmp[0] in "0123456789":
+ covered[lineno] = int(segments[0].strip())
+ elif tmp[0] == '-':
+ # remember certain non-executed lines
+ code = segments[2].strip()
+ if len(code) == 0 or code == "{" or code == "}" or \
+ code.startswith("//") or code == 'else':
+ noncode.add( lineno )
+ elif tmp.startswith('branch'):
+ fields = line.split()
+ try:
+ count = int(fields[3])
+ branches.setdefault(lineno, {})[int(fields[1])] = count
+ except:
+ # We ignore branches that were "never executed"
+ pass
+ elif tmp.startswith('call'):
+ pass
+ elif tmp.startswith('function'):
+ pass
+ elif tmp[0] == 'f':
+ pass
+ #if first_record:
+ #first_record=False
+ #uncovered.add(prev)
+ #if prev in uncovered:
+ #tokens=re.split('[ \t]+',tmp)
+ #if tokens[3] != "0":
+ #uncovered.remove(prev)
+ #prev = int(segments[1].strip())
+ #first_record=True
+ else:
+ sys.stderr.write(
+ "(WARNING) Unrecognized GCOV output: '%s'\n"
+ "\tThis is indicitive of a gcov output parse error.\n"
+ "\tPlease report this to the gcovr developers." % tmp )
+ ##print 'uncovered',uncovered
+ ##print 'covered',covered
+ ##print 'branches',branches
+ ##print 'noncode',noncode
+ #
+ # If the file is already in covdata, then we
+ # remove lines that are covered here. Otherwise,
+ # initialize covdata
+ #
+ if not fname in covdata:
+ covdata[fname] = CoverageData(fname,uncovered,uncovered_exceptional,covered,branches,noncode)
+ else:
+ covdata[fname].update(uncovered,uncovered_exceptional,covered,branches,noncode)
+ INPUT.close()
+
+#
+# Process a datafile (generated by running the instrumented application)
+# and run gcov with the corresponding arguments
+#
+# This is trickier than it sounds: The gcda/gcno files are stored in the
+# same directory as the object files; however, gcov must be run from the
+# same directory where gcc/g++ was run. Normally, the user would know
+# where gcc/g++ was invoked from and could tell gcov the path to the
+# object (and gcda) files with the --object-directory command.
+# Unfortunately, we do everything backwards: gcovr looks for the gcda
+# files and then has to infer the original gcc working directory.
+#
+# In general, (but not always) we can assume that the gcda file is in a
+# subdirectory of the original gcc working directory, so we will first
+# try ".", and on error, move up the directory tree looking for the
+# correct working directory (letting gcov's own error codes dictate when
+# we hit the right directory). This covers 90+% of the "normal" cases.
+# The exception to this is if gcc was invoked with "-o ../[...]" (i.e.,
+# the object directory was a peer (not a parent/child) of the cwd. In
+# this case, things are really tough. We accept an argument
+# (--object-directory) that SHOULD BE THE SAME as the one povided to
+# gcc. We will then walk that path (backwards) in the hopes of
+# identifying the original gcc working directory (there is a bit of
+# trial-and-error here)
+#
+def process_datafile(filename, covdata, options):
+ #
+ # Launch gcov
+ #
+ abs_filename = os.path.abspath(filename)
+ (dirname,fname) = os.path.split(abs_filename)
+ #(name,ext) = os.path.splitext(base)
+
+ potential_wd = []
+ errors=[]
+ Done = False
+
+ if options.objdir:
+ src_components = abs_filename.split(os.sep)
+ components = normpath(options.objdir).split(os.sep)
+ idx = 1
+ while idx <= len(components):
+ if idx > len(src_components):
+ break
+ if components[-1*idx] != src_components[-1*idx]:
+ break
+ idx += 1
+ if idx > len(components):
+ pass # a parent dir; the normal process will find it
+ elif components[-1*idx] == '..':
+ dirs = [ os.path.join(src_components[:len(src_components)-idx+1]) ]
+ while idx <= len(components) and components[-1*idx] == '..':
+ tmp = []
+ for d in dirs:
+ for f in os.listdir(d):
+ x = os.path.join(d,f)
+ if os.path.isdir(x):
+ tmp.append(x)
+ dirs = tmp
+ idx += 1
+ potential_wd = dirs
+ else:
+ if components[0] == '':
+ # absolute path
+ tmp = [ options.objdir ]
+ else:
+ # relative path: check relative to both the cwd and the
+ # gcda file
+ tmp = [ os.path.join(x, options.objdir) for x in
+ [os.path.dirname(abs_filename), os.getcwd()] ]
+ potential_wd = [ testdir for testdir in tmp
+ if os.path.isdir(testdir) ]
+ if len(potential_wd) == 0:
+ errors.append("ERROR: cannot identify the location where GCC "
+ "was run using --object-directory=%s\n" %
+ options.objdir)
+ # Revert to the normal
+ #sys.exit(1)
+
+ # no objdir was specified (or it was a parent dir); walk up the dir tree
+ if len(potential_wd) == 0:
+ wd = os.path.split(abs_filename)[0]
+ while True:
+ potential_wd.append(wd)
+ wd = os.path.split(wd)[0]
+ if wd == potential_wd[-1]:
+ break
+
+ cmd = [ gcov_cmd, abs_filename,
+ "--branch-counts", "--branch-probabilities", "--preserve-paths",
+ '--object-directory', dirname ]
+
+ # NB: We are lazy English speakers, so we will only parse English output
+ env = dict(os.environ)
+ env['LC_ALL'] = 'en_US'
+
+
+ while len(potential_wd) > 0 and not Done:
+ # NB: either len(potential_wd) == 1, or all entires are absolute
+ # paths, so we don't have to chdir(starting_dir) at every
+ # iteration.
+ os.chdir(potential_wd.pop(0))
+
+
+ #if options.objdir:
+ # cmd.extend(["--object-directory", Template(options.objdir).substitute(filename=filename, head=dirname, tail=base, root=name, ext=ext)])
+
+ if options.verbose:
+ sys.stdout.write("Running gcov: '%s' in '%s'\n" % ( ' '.join(cmd), os.getcwd() ))
+ (out, err) = subprocess.Popen( cmd, env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE ).communicate()
+ out=out.decode('utf-8')
+ err=err.decode('utf-8')
+
+ # find the files that gcov created
+ gcov_files = {'active':[], 'filter':[], 'exclude':[]}
+ for line in out.splitlines():
+ found = output_re.search(line.strip())
+ if found is not None:
+ fname = found.group(1)
+ if not options.gcov_filter.match(fname):
+ if options.verbose:
+ sys.stdout.write("Filtering gcov file %s\n" % fname)
+ gcov_files['filter'].append(fname)
+ continue
+ exclude=False
+ for i in range(0,len(options.gcov_exclude)):
+ if options.gcov_exclude[i].match(options.gcov_filter.sub('',fname)) or \
+ options.gcov_exclude[i].match(fname) or \
+ options.gcov_exclude[i].match(os.path.abspath(fname)):
+ exclude=True
+ break
+ if not exclude:
+ gcov_files['active'].append(fname)
+ elif options.verbose:
+ sys.stdout.write("Excluding gcov file %s\n" % fname)
+ gcov_files['exclude'].append(fname)
+
+ if source_re.search(err):
+ # gcov tossed errors: try the next potential_wd
+ errors.append(err)
+ else:
+ # Process *.gcov files
+ for fname in gcov_files['active']:
+ process_gcov_data(fname, covdata, options)
+ Done = True
+
+ if not options.keep:
+ for group in gcov_files.values():
+ for fname in group:
+ if os.path.exists(fname):
+ # Only remove files that actually exist.
+ os.remove(fname)
+
+ os.chdir(starting_dir)
+ if options.delete:
+ if not abs_filename.endswith('gcno'):
+ os.remove(abs_filename)
+
+ if not Done:
+ sys.stderr.write(
+ "(WARNING) GCOV produced the following errors processing %s:\n"
+ "\t %s"
+ "\t(gcovr could not infer a working directory that resolved it.)\n"
+ % ( filename, "\t ".join(errors) ) )
+
+#
+# Produce the classic gcovr text report
+#
+def print_text_report(covdata):
+ def _num_uncovered(key):
+ (total, covered, percent) = covdata[key].coverage()
+ return total - covered
+ def _percent_uncovered(key):
+ (total, covered, percent) = covdata[key].coverage()
+ if covered:
+ return -1.0*covered/total
+ else:
+ return total or 1e6
+ def _alpha(key):
+ return key
+
+ if options.output:
+ OUTPUT = open(options.output,'w')
+ else:
+ OUTPUT = sys.stdout
+ total_lines=0
+ total_covered=0
+ # Header
+ OUTPUT.write("-"*78 + '\n')
+ a = options.show_branch and "Branches" or "Lines"
+ b = options.show_branch and "Taken" or "Exec"
+ c = "Missing"
+ OUTPUT.write("File".ljust(40) + a.rjust(8) + b.rjust(8)+ " Cover " + c + "\n")
+ OUTPUT.write("-"*78 + '\n')
+
+ # Data
+ keys = list(covdata.keys())
+ keys.sort(key=options.sort_uncovered and _num_uncovered or \
+ options.sort_percent and _percent_uncovered or _alpha)
+ for key in keys:
+ (t, n, txt) = covdata[key].summary()
+ total_lines += t
+ total_covered += n
+ OUTPUT.write(txt + '\n')
+
+ # Footer & summary
+ OUTPUT.write("-"*78 + '\n')
+ percent = total_lines and str(int(100.0*total_covered/total_lines)) or "--"
+ OUTPUT.write("TOTAL".ljust(40) + str(total_lines).rjust(8) + \
+ str(total_covered).rjust(8) + str(percent).rjust(6)+"%" + '\n')
+ OUTPUT.write("-"*78 + '\n')
+
+ # Close logfile
+ if options.output:
+ OUTPUT.close()
+
+#
+# Produce an XML report in the Cobertura format
+#
+def print_xml_report(covdata):
+ branchTotal = 0
+ branchCovered = 0
+ lineTotal = 0
+ lineCovered = 0
+
+ options.show_branch = True
+ for key in covdata.keys():
+ (total, covered, percent) = covdata[key].coverage()
+ branchTotal += total
+ branchCovered += covered
+
+ options.show_branch = False
+ for key in covdata.keys():
+ (total, covered, percent) = covdata[key].coverage()
+ lineTotal += total
+ lineCovered += covered
+
+ impl = xml.dom.minidom.getDOMImplementation()
+ docType = impl.createDocumentType(
+ "coverage", None,
+ "http://cobertura.sourceforge.net/xml/coverage-03.dtd" )
+ doc = impl.createDocument(None, "coverage", docType)
+ root = doc.documentElement
+ root.setAttribute( "line-rate", lineTotal == 0 and '0.0' or
+ str(float(lineCovered) / lineTotal) )
+ root.setAttribute( "branch-rate", branchTotal == 0 and '0.0' or
+ str(float(branchCovered) / branchTotal) )
+ root.setAttribute( "timestamp", str(int(time.time())) )
+ root.setAttribute( "version", "gcovr %s" % (version_str(),) )
+
+ # Generate the <sources> element: this is either the root directory
+ # (specified by --root), or the CWD.
+ sources = doc.createElement("sources")
+ root.appendChild(sources)
+
+ # Generate the coverage output (on a per-package basis)
+ packageXml = doc.createElement("packages")
+ root.appendChild(packageXml)
+ packages = {}
+ source_dirs = set()
+
+ keys = list(covdata.keys())
+ keys.sort()
+ for f in keys:
+ data = covdata[f]
+ dir = options.filter.sub('',f)
+ if f.endswith(dir):
+ src_path = f[:-1*len(dir)]
+ if len(src_path) > 0:
+ while dir.startswith(os.path.sep):
+ src_path += os.path.sep
+ dir = dir[len(os.path.sep):]
+ source_dirs.add(src_path)
+ else:
+ # Do no truncation if the filter does not start matching at
+ # the beginning of the string
+ dir = f
+ (dir, fname) = os.path.split(dir)
+
+ package = packages.setdefault(
+ dir, [ doc.createElement("package"), {},
+ 0, 0, 0, 0 ] )
+ c = doc.createElement("class")
+ lines = doc.createElement("lines")
+ c.appendChild(lines)
+
+ class_lines = 0
+ class_hits = 0
+ class_branches = 0
+ class_branch_hits = 0
+ for line in data.all_lines:
+ hits = data.covered.get(line, 0)
+ class_lines += 1
+ if hits > 0:
+ class_hits += 1
+ l = doc.createElement("line")
+ l.setAttribute("number", str(line))
+ l.setAttribute("hits", str(hits))
+ branches = data.branches.get(line)
+ if branches is None:
+ l.setAttribute("branch", "false")
+ else:
+ b_hits = 0
+ for v in branches.values():
+ if v > 0:
+ b_hits += 1
+ coverage = 100*b_hits/len(branches)
+ l.setAttribute("branch", "true")
+ l.setAttribute( "condition-coverage",
+ "%i%% (%i/%i)" %
+ (coverage, b_hits, len(branches)) )
+ cond = doc.createElement('condition')
+ cond.setAttribute("number", "0")
+ cond.setAttribute("type", "jump")
+ cond.setAttribute("coverage", "%i%%" % ( coverage ) )
+ class_branch_hits += b_hits
+ class_branches += float(len(branches))
+ conditions = doc.createElement("conditions")
+ conditions.appendChild(cond)
+ l.appendChild(conditions)
+
+ lines.appendChild(l)
+
+ className = fname.replace('.', '_')
+ c.setAttribute("name", className)
+ c.setAttribute("filename", os.path.join(dir, fname))
+ c.setAttribute("line-rate", str(class_hits / (1.0*class_lines or 1.0)))
+ c.setAttribute( "branch-rate",
+ str(class_branch_hits / (1.0*class_branches or 1.0)) )
+ c.setAttribute("complexity", "0.0")
+
+ package[1][className] = c
+ package[2] += class_hits
+ package[3] += class_lines
+ package[4] += class_branch_hits
+ package[5] += class_branches
+
+ for packageName, packageData in packages.items():
+ package = packageData[0];
+ packageXml.appendChild(package)
+ classes = doc.createElement("classes")
+ package.appendChild(classes)
+ classNames = list(packageData[1].keys())
+ classNames.sort()
+ for className in classNames:
+ classes.appendChild(packageData[1][className])
+ package.setAttribute("name", packageName.replace(os.sep, '.'))
+ package.setAttribute("line-rate", str(packageData[2]/(1.0*packageData[3] or 1.0)))
+ package.setAttribute( "branch-rate", str(packageData[4] / (1.0*packageData[5] or 1.0) ))
+ package.setAttribute("complexity", "0.0")
+
+
+ # Populate the <sources> element: this is either the root directory
+ # (specified by --root), or relative directories based
+ # on the filter, or the CWD
+ if options.root is not None:
+ source = doc.createElement("source")
+ source.appendChild(doc.createTextNode(options.root.strip()))
+ sources.appendChild(source)
+ elif len(source_dirs) > 0:
+ cwd = os.getcwd()
+ for d in source_dirs:
+ source = doc.createElement("source")
+ if d.startswith(cwd):
+ reldir = d[len(cwd):].lstrip(os.path.sep)
+ elif cwd.startswith(d):
+ i = 1
+ while normpath(d) != normpath(os.path.join(*tuple([cwd]+['..']*i))):
+ i += 1
+ reldir = os.path.join(*tuple(['..']*i))
+ else:
+ reldir = d
+ source.appendChild(doc.createTextNode(reldir.strip()))
+ sources.appendChild(source)
+ else:
+ source = doc.createElement("source")
+ source.appendChild(doc.createTextNode('.'))
+ sources.appendChild(source)
+
+ if options.prettyxml:
+ import textwrap
+ lines = doc.toprettyxml(" ").split('\n')
+ for i in xrange(len(lines)):
+ n=0
+ while n < len(lines[i]) and lines[i][n] == " ":
+ n += 1
+ lines[i] = "\n".join(textwrap.wrap(lines[i], 78, break_long_words=False, break_on_hyphens=False, subsequent_indent=" "+ n*" "))
+ xmlString = "\n".join(lines)
+ #print textwrap.wrap(doc.toprettyxml(" "), 80)
+ else:
+ xmlString = doc.toprettyxml(indent="")
+ if options.output is None:
+ sys.stdout.write(xmlString+'\n')
+ else:
+ OUTPUT = open(options.output, 'w')
+ OUTPUT.write(xmlString +'\n')
+ OUTPUT.close()
+
+
+##
+## MAIN
+##
+
+#
+# Create option parser
+#
+parser = OptionParser()
+parser.add_option("--version",
+ help="Print the version number, then exit",
+ action="store_true",
+ dest="version",
+ default=False)
+parser.add_option("-v","--verbose",
+ help="Print progress messages",
+ action="store_true",
+ dest="verbose",
+ default=False)
+parser.add_option('--object-directory',
+ help="Specify the directory that contains the gcov data files. gcovr must be able to identify the path between the *.gcda files and the directory where gcc was originally run. Normally, gcovr can guess correctly. This option overrides gcovr's normal path detection and can specify either the path from gcc to the gcda file (i.e. what was passed to gcc's '-o' option), or the path from the gcda file to gcc's original working directory.",
+ action="store",
+ dest="objdir",
+ default=None)
+parser.add_option("-o","--output",
+ help="Print output to this filename",
+ action="store",
+ dest="output",
+ default=None)
+parser.add_option("-k","--keep",
+ help="Keep the temporary *.gcov files generated by gcov. By default, these are deleted.",
+ action="store_true",
+ dest="keep",
+ default=False)
+parser.add_option("-d","--delete",
+ help="Delete the coverage files after they are processed. These are generated by the users's program, and by default gcovr does not remove these files.",
+ action="store_true",
+ dest="delete",
+ default=False)
+parser.add_option("-f","--filter",
+ help="Keep only the data files that match this regular expression",
+ action="store",
+ dest="filter",
+ default=None)
+parser.add_option("-e","--exclude",
+ help="Exclude data files that match this regular expression",
+ action="append",
+ dest="exclude",
+ default=[])
+parser.add_option("--gcov-filter",
+ help="Keep only gcov data files that match this regular expression",
+ action="store",
+ dest="gcov_filter",
+ default=None)
+parser.add_option("--gcov-exclude",
+ help="Exclude gcov data files that match this regular expression",
+ action="append",
+ dest="gcov_exclude",
+ default=[])
+parser.add_option("-r","--root",
+ help="Defines the root directory. This is used to filter the files, and to standardize the output.",
+ action="store",
+ dest="root",
+ default=None)
+parser.add_option("-x","--xml",
+ help="Generate XML instead of the normal tabular output.",
+ action="store_true",
+ dest="xml",
+ default=False)
+parser.add_option("--xml-pretty",
+ help="Generate pretty XML instead of the normal dense format.",
+ action="store_true",
+ dest="prettyxml",
+ default=False)
+parser.add_option("-b","--branches",
+ help="Tabulate the branch coverage instead of the line coverage.",
+ action="store_true",
+ dest="show_branch",
+ default=None)
+parser.add_option("-u","--sort-uncovered",
+ help="Sort entries by increasing number of uncovered lines.",
+ action="store_true",
+ dest="sort_uncovered",
+ default=None)
+parser.add_option("-p","--sort-percentage",
+ help="Sort entries by decreasing percentage of covered lines.",
+ action="store_true",
+ dest="sort_percent",
+ default=None)
+parser.usage="gcovr [options]"
+parser.description="A utility to run gcov and generate a simple report that summarizes the coverage"
+#
+# Process options
+#
+(options, args) = parser.parse_args(args=sys.argv)
+if options.version:
+ sys.stdout.write(
+ "gcovr %s\n"
+ "\n"
+ "Copyright (2008) Sandia Corporation. Under the terms of Contract\n"
+ "DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government\n"
+ "retains certain rights in this software.\n"
+ % (version_str(),) )
+ sys.exit(0)
+if options.objdir:
+ tmp = options.objdir.replace('/',os.sep).replace('\\',os.sep)
+ while os.sep+os.sep in tmp:
+ tmp = tmp.replace(os.sep+os.sep, os.sep)
+ if normpath(options.objdir) != tmp:
+ sys.stderr.write(
+ "(WARNING) relative referencing in --object-directory.\n"
+ "\tthis could cause strange errors when gcovr attempts to\n"
+ "\tidentify the original gcc working directory.\n")
+#
+# Setup filters
+#
+for i in range(0,len(options.exclude)):
+ options.exclude[i] = re.compile(options.exclude[i])
+if options.filter is not None:
+ options.filter = re.compile(options.filter)
+elif options.root is not None:
+ if not options.root:
+ sys.stderr.write(
+ "(ERROR) empty --root option.\n"
+ "\tRoot specifies the path to the root directory of your project.\n"
+ "\tThis option cannot be an empty string.\n")
+ sys.exit(1)
+ options.filter = re.compile(re.escape(os.path.abspath(options.root)+os.sep))
+if options.filter is None:
+ options.filter = re.compile('')
+#
+for i in range(0,len(options.gcov_exclude)):
+ options.gcov_exclude[i] = re.compile(options.gcov_exclude[i])
+if options.gcov_filter is not None:
+ options.gcov_filter = re.compile(options.gcov_filter)
+else:
+ options.gcov_filter = re.compile('')
+#
+# Get data files
+#
+if len(args) == 1:
+ datafiles = get_datafiles(["."], options)
+else:
+ datafiles = get_datafiles(args[1:], options)
+#
+# Get coverage data
+#
+covdata = {}
+for file in datafiles:
+ process_datafile(file,covdata,options)
+if options.verbose:
+ sys.stdout.write("Gathered coveraged data for "+str(len(covdata))+" files\n")
+#
+# Print report
+#
+if options.xml or options.prettyxml:
+ print_xml_report(covdata)
+else:
+ print_text_report(covdata)
diff --git a/Tools/Scripts/webkitpy/tool/grammar_unittest.py b/Tools/Scripts/webkitpy/tool/grammar_unittest.py
index cab71db01..dd8081f32 100644
--- a/Tools/Scripts/webkitpy/tool/grammar_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/grammar_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.tool.grammar import join_with_separators
@@ -36,6 +36,3 @@ class GrammarTest(unittest.TestCase):
self.assertEqual(join_with_separators(["one"]), "one")
self.assertEqual(join_with_separators(["one", "two"]), "one and two")
self.assertEqual(join_with_separators(["one", "two", "three"]), "one, two, and three")
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/tool/main.py b/Tools/Scripts/webkitpy/tool/main.py
index 68348a05a..3fa6e6b5b 100755..100644
--- a/Tools/Scripts/webkitpy/tool/main.py
+++ b/Tools/Scripts/webkitpy/tool/main.py
@@ -48,6 +48,7 @@ class WebKitPatch(MultiCommandTool, Host):
make_option("--status-host", action="store", dest="status_host", type="string", help="Hostname (e.g. localhost or commit.webkit.org) where status updates should be posted."),
make_option("--bot-id", action="store", dest="bot_id", type="string", help="Identifier for this bot (if multiple bots are running for a queue)"),
make_option("--irc-password", action="store", dest="irc_password", type="string", help="Password to use when communicating via IRC."),
+ make_option("--seconds-to-sleep", action="store", default=120, type="int", help="Number of seconds to sleep in the task queue."),
make_option("--port", action="store", dest="port", default=None, help="Specify a port (e.g., mac, qt, gtk, ...)."),
]
@@ -61,8 +62,7 @@ class WebKitPatch(MultiCommandTool, Host):
self._irc = None
self._deprecated_port = None
- # FIXME: Rename this deprecated_port()
- def port(self):
+ def deprecated_port(self):
return self._deprecated_port
def path(self):
diff --git a/Tools/Scripts/webkitpy/tool/mocktool.py b/Tools/Scripts/webkitpy/tool/mocktool.py
index b8f0976bc..175d1b848 100644
--- a/Tools/Scripts/webkitpy/tool/mocktool.py
+++ b/Tools/Scripts/webkitpy/tool/mocktool.py
@@ -71,7 +71,7 @@ class MockTool(MockHost):
self.irc_password = "MOCK irc password"
self.wakeup_event = threading.Event()
- def port(self):
+ def deprecated_port(self):
return self._deprecated_port
def path(self):
@@ -83,6 +83,3 @@ class MockTool(MockHost):
def irc(self):
return self._irc
-
- def buildbot_for_builder_name(self, name):
- return MockBuildBot()
diff --git a/Tools/Scripts/webkitpy/tool/mocktool_unittest.py b/Tools/Scripts/webkitpy/tool/mocktool_unittest.py
index cceaa2e0a..35fdd3aac 100644
--- a/Tools/Scripts/webkitpy/tool/mocktool_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/mocktool_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from mocktool import MockOptions
@@ -53,7 +53,3 @@ class MockOptionsTest(unittest.TestCase):
# Test that keyword arguments work in the constructor.
options = MockOptions(foo='bar')
self.assertEqual(options.foo, 'bar')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/tool/multicommandtool.py b/Tools/Scripts/webkitpy/tool/multicommandtool.py
index e2f91a7da..01b022f32 100644
--- a/Tools/Scripts/webkitpy/tool/multicommandtool.py
+++ b/Tools/Scripts/webkitpy/tool/multicommandtool.py
@@ -48,11 +48,12 @@ class TryAgain(Exception):
class Command(object):
name = None
show_in_main_help = False
- def __init__(self, help_text, argument_names=None, options=None, long_help=None, requires_local_commits=False):
- self.help_text = help_text
- self.long_help = long_help
- self.argument_names = argument_names
- self.required_arguments = self._parse_required_arguments(argument_names)
+ help_text = None
+ long_help = None
+ argument_names = None
+
+ def __init__(self, options=None, requires_local_commits=False):
+ self.required_arguments = self._parse_required_arguments(self.argument_names)
self.options = options
self.requires_local_commits = requires_local_commits
self._tool = None
@@ -139,15 +140,6 @@ class Command(object):
return self.check_arguments_and_execute(options, args)
-# FIXME: This should just be rolled into Command. help_text and argument_names do not need to be instance variables.
-class AbstractDeclarativeCommand(Command):
- help_text = None
- argument_names = None
- long_help = None
- def __init__(self, options=None, **kwargs):
- Command.__init__(self, self.help_text, self.argument_names, options=options, long_help=self.long_help, **kwargs)
-
-
class HelpPrintingOptionParser(OptionParser):
def __init__(self, epilog_method=None, *args, **kwargs):
self.epilog_method = epilog_method
@@ -168,7 +160,7 @@ class HelpPrintingOptionParser(OptionParser):
return ""
-class HelpCommand(AbstractDeclarativeCommand):
+class HelpCommand(Command):
name = "help"
help_text = "Display information about this program or its subcommands"
argument_names = "[COMMAND]"
@@ -177,7 +169,7 @@ class HelpCommand(AbstractDeclarativeCommand):
options = [
make_option("-a", "--all-commands", action="store_true", dest="show_all_commands", help="Print all available commands"),
]
- AbstractDeclarativeCommand.__init__(self, options)
+ Command.__init__(self, options)
self.show_all_commands = False # A hack used to pass --all-commands to _help_epilog even though it's called by the OptionParser.
def _help_epilog(self):
diff --git a/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py b/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py
index ecb1df007..a498e6929 100644
--- a/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py
@@ -27,7 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
-import unittest
+import unittest2 as unittest
from optparse import make_option
@@ -38,8 +38,9 @@ from webkitpy.tool.multicommandtool import MultiCommandTool, Command, TryAgain
class TrivialCommand(Command):
name = "trivial"
show_in_main_help = True
+ help_text = "help text"
def __init__(self, **kwargs):
- Command.__init__(self, "help text", **kwargs)
+ Command.__init__(self, **kwargs)
def execute(self, options, args, tool):
pass
@@ -53,9 +54,10 @@ class UncommonCommand(TrivialCommand):
class LikesToRetry(Command):
name = "likes-to-retry"
show_in_main_help = True
+ help_text = "help text"
def __init__(self, **kwargs):
- Command.__init__(self, "help text", **kwargs)
+ Command.__init__(self, **kwargs)
self.execute_count = 0
def execute(self, options, args, tool):
@@ -66,9 +68,11 @@ class LikesToRetry(Command):
class CommandTest(unittest.TestCase):
def test_name_with_arguments(self):
- command_with_args = TrivialCommand(argument_names="ARG1 ARG2")
+ TrivialCommand.argument_names = "ARG1 ARG2"
+ command_with_args = TrivialCommand()
self.assertEqual(command_with_args.name_with_arguments(), "trivial ARG1 ARG2")
+ TrivialCommand.argument_names = None
command_with_args = TrivialCommand(options=[make_option("--my_option")])
self.assertEqual(command_with_args.name_with_arguments(), "trivial [options]")
@@ -80,10 +84,12 @@ class CommandTest(unittest.TestCase):
self.assertRaises(Exception, Command._parse_required_arguments, "[ARG1 ARG2]")
def test_required_arguments(self):
- two_required_arguments = TrivialCommand(argument_names="ARG1 ARG2 [ARG3]")
+ TrivialCommand.argument_names = "ARG1 ARG2 [ARG3]"
+ two_required_arguments = TrivialCommand()
expected_logs = "2 arguments required, 1 argument provided. Provided: 'foo' Required: ARG1 ARG2\nSee 'trivial-tool help trivial' for usage.\n"
exit_code = OutputCapture().assert_outputs(self, two_required_arguments.check_arguments_and_execute, [None, ["foo"], TrivialTool()], expected_logs=expected_logs)
self.assertEqual(exit_code, 1)
+ TrivialCommand.argument_names = None
class TrivialTool(MultiCommandTool):
@@ -167,11 +173,8 @@ See 'trivial-tool help COMMAND' for more information on a specific command.
def test_command_help(self):
- command_with_options = TrivialCommand(options=[make_option("--my_option")], long_help="LONG HELP")
+ TrivialCommand.long_help = "LONG HELP"
+ command_with_options = TrivialCommand(options=[make_option("--my_option")])
tool = TrivialTool(commands=[command_with_options])
expected_subcommand_help = "trivial [options] help text\n\nLONG HELP\n\nOptions:\n --my_option=MY_OPTION\n\n"
self._assert_tool_main_outputs(tool, ["tool", "help", "trivial"], expected_subcommand_help)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py b/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
index 77068acf4..b06984660 100644
--- a/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
+++ b/Tools/Scripts/webkitpy/tool/servers/gardeningserver.py
@@ -32,7 +32,7 @@ import urllib
from webkitpy.common.memoized import memoized
from webkitpy.tool.servers.reflectionhandler import ReflectionHandler
-from webkitpy.layout_tests.port import builders
+from webkitpy.port import builders
_log = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ class GardeningHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer
class GardeningHTTPRequestHandler(ReflectionHandler):
STATIC_FILE_NAMES = frozenset()
- STATIC_FILE_EXTENSIONS = ('.js', '.css', '.html', '.gif', '.png')
+ STATIC_FILE_EXTENSIONS = ('.js', '.css', '.html', '.gif', '.png', '.ico')
STATIC_FILE_DIRECTORY = os.path.join(
os.path.dirname(__file__),
diff --git a/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py b/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
index 438cc0583..9f9efe807 100644
--- a/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/servers/gardeningserver_unittest.py
@@ -28,11 +28,11 @@
import json
import sys
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.layout_tests.models.test_configuration import *
-from webkitpy.layout_tests.port import builders
+from webkitpy.port import builders
from webkitpy.thirdparty.mock import Mock
from webkitpy.tool.mocktool import MockTool
from webkitpy.common.system.executive_mock import MockExecutive
diff --git a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
index 9e9c379d6..41a32ba54 100644
--- a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
+++ b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver.py
@@ -32,7 +32,8 @@ import os.path
import BaseHTTPServer
from webkitpy.common.host import Host # FIXME: This should not be needed!
-from webkitpy.layout_tests.port.base import Port
+from webkitpy.common.system.executive import ScriptError
+from webkitpy.port.base import Port
from webkitpy.tool.servers.reflectionhandler import ReflectionHandler
@@ -114,13 +115,13 @@ def _rebaseline_test(test_file, baseline_target, baseline_move_to, test_config,
destination_path = filesystem.join(
target_expectations_directory, destination_file)
filesystem.copyfile(source_path, destination_path)
- exit_code = scm.add(destination_path, return_exit_code=True)
- if exit_code:
+ try:
+ scm.add(destination_path)
+ log(' Updated %s' % destination_file)
+ except ScriptError, error:
log(' Could not update %s in SCM, exit code %d' %
- (destination_file, exit_code))
+ (destination_file, error.exit_code))
return False
- else:
- log(' Updated %s' % destination_file)
return True
@@ -150,13 +151,13 @@ def _move_test_baselines(test_file, extensions_to_move, source_platform, destina
source_path = filesystem.join(source_directory, file_name)
destination_path = filesystem.join(destination_directory, file_name)
filesystem.copyfile(source_path, destination_path)
- exit_code = test_config.scm.add(destination_path, return_exit_code=True)
- if exit_code:
+ try:
+ test_config.scm.add(destination_path)
+ log(' Moved %s' % file_name)
+ except ScriptError, error:
log(' Could not update %s in SCM, exit code %d' %
- (file_name, exit_code))
+ (file_name, error.exit_code))
return False
- else:
- log(' Moved %s' % file_name)
return True
diff --git a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py
index f5c1cbf5e..721154cb6 100644
--- a/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py
@@ -27,12 +27,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import json
-import unittest
+import unittest2 as unittest
from webkitpy.common.net import resultsjsonparser_unittest
from webkitpy.common.host_mock import MockHost
from webkitpy.layout_tests.layout_package.json_results_generator import strip_json_wrapper
-from webkitpy.layout_tests.port.base import Port
+from webkitpy.port.base import Port
from webkitpy.tool.commands.rebaselineserver import TestConfig, RebaselineServer
from webkitpy.tool.servers import rebaselineserver
@@ -211,7 +211,7 @@ class RebaselineTestTest(unittest.TestCase):
server._test_config = get_test_config()
server._gather_baselines(results_json)
self.assertEqual(results_json['tests']['svg/dynamic-updates/SVGFEDropShadowElement-dom-stdDeviation-attr.html']['state'], 'needs_rebaseline')
- self.assertFalse('prototype-chocolate.html' in results_json['tests'])
+ self.assertNotIn('prototype-chocolate.html', results_json['tests'])
def _assertRebaseline(self, test_files, results_files, test_name, baseline_target, baseline_move_to, expected_success, expected_log):
log = []
@@ -234,7 +234,7 @@ class GetActualResultFilesTest(unittest.TestCase):
'fast/text2-actual.txt',
'fast/text-notactual.txt',
))
- self.assertEqual(
+ self.assertItemsEqual(
('text-actual.txt',),
rebaselineserver._get_actual_result_files(
'fast/text.html', test_config))
diff --git a/Tools/Scripts/webkitpy/tool/servers/reflectionhandler_unittest.py b/Tools/Scripts/webkitpy/tool/servers/reflectionhandler_unittest.py
index d269dfcf5..e1d562364 100644
--- a/Tools/Scripts/webkitpy/tool/servers/reflectionhandler_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/servers/reflectionhandler_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.tool.servers.reflectionhandler import ReflectionHandler
diff --git a/Tools/Scripts/webkitpy/tool/steps/__init__.py b/Tools/Scripts/webkitpy/tool/steps/__init__.py
index 56429e8fe..655f7d50a 100644
--- a/Tools/Scripts/webkitpy/tool/steps/__init__.py
+++ b/Tools/Scripts/webkitpy/tool/steps/__init__.py
@@ -35,23 +35,23 @@ from webkitpy.tool.steps.attachtobug import AttachToBug
from webkitpy.tool.steps.build import Build
from webkitpy.tool.steps.checkstyle import CheckStyle
from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory
-from webkitpy.tool.steps.cleanworkingdirectorywithlocalcommits import CleanWorkingDirectoryWithLocalCommits
from webkitpy.tool.steps.closebug import CloseBug
from webkitpy.tool.steps.closebugforlanddiff import CloseBugForLandDiff
from webkitpy.tool.steps.closepatch import ClosePatch
from webkitpy.tool.steps.commit import Commit
from webkitpy.tool.steps.confirmdiff import ConfirmDiff
from webkitpy.tool.steps.createbug import CreateBug
+from webkitpy.tool.steps.discardlocalchanges import DiscardLocalChanges
from webkitpy.tool.steps.editchangelog import EditChangeLog
from webkitpy.tool.steps.ensurebugisopenandassigned import EnsureBugIsOpenAndAssigned
from webkitpy.tool.steps.ensurelocalcommitifneeded import EnsureLocalCommitIfNeeded
+from webkitpy.tool.steps.haslanded import HasLanded
from webkitpy.tool.steps.obsoletepatches import ObsoletePatches
from webkitpy.tool.steps.options import Options
from webkitpy.tool.steps.postdiff import PostDiff
from webkitpy.tool.steps.postdiffforcommit import PostDiffForCommit
from webkitpy.tool.steps.postdiffforrevert import PostDiffForRevert
from webkitpy.tool.steps.preparechangelog import PrepareChangeLog
-from webkitpy.tool.steps.preparechangelogfordepsroll import PrepareChangeLogForDEPSRoll
from webkitpy.tool.steps.preparechangelogforrevert import PrepareChangeLogForRevert
from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle
from webkitpy.tool.steps.reopenbugafterrollout import ReopenBugAfterRollout
@@ -60,6 +60,5 @@ from webkitpy.tool.steps.runtests import RunTests
from webkitpy.tool.steps.suggestreviewers import SuggestReviewers
from webkitpy.tool.steps.update import Update
from webkitpy.tool.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer
-from webkitpy.tool.steps.updatechromiumdeps import UpdateChromiumDEPS
from webkitpy.tool.steps.validatechangelogs import ValidateChangeLogs
from webkitpy.tool.steps.validatereviewer import ValidateReviewer
diff --git a/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py b/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py
index 9fab6f438..12be0bee2 100644
--- a/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py
@@ -21,7 +21,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.
-import unittest
+import unittest2 as unittest
from webkitpy.tool.steps.addsvnmimetypeforpng import AddSvnMimetypeForPng
from webkitpy.common.system.filesystem_mock import MockFileSystem
diff --git a/Tools/Scripts/webkitpy/tool/steps/applywatchlist_unittest.py b/Tools/Scripts/webkitpy/tool/steps/applywatchlist_unittest.py
index a978f4164..a740c3d3c 100644
--- a/Tools/Scripts/webkitpy/tool/steps/applywatchlist_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/applywatchlist_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
diff --git a/Tools/Scripts/webkitpy/tool/steps/build.py b/Tools/Scripts/webkitpy/tool/steps/build.py
index a2a627229..b02830ca2 100644
--- a/Tools/Scripts/webkitpy/tool/steps/build.py
+++ b/Tools/Scripts/webkitpy/tool/steps/build.py
@@ -48,7 +48,7 @@ class Build(AbstractStep):
environment.disable_gcc_smartquotes()
env = environment.to_dictionary()
- build_webkit_command = self._tool.port().build_webkit_command(build_style=build_style)
+ build_webkit_command = self._tool.deprecated_port().build_webkit_command(build_style=build_style)
self._tool.executive.run_and_throw_if_fail(build_webkit_command, self._options.quiet,
cwd=self._tool.scm().checkout_root, env=env)
diff --git a/Tools/Scripts/webkitpy/tool/steps/checkstyle.py b/Tools/Scripts/webkitpy/tool/steps/checkstyle.py
index 0cb15f4c1..cec8a8132 100644
--- a/Tools/Scripts/webkitpy/tool/steps/checkstyle.py
+++ b/Tools/Scripts/webkitpy/tool/steps/checkstyle.py
@@ -57,7 +57,7 @@ class CheckStyle(AbstractStep):
args.append(self._options.check_style_filter)
try:
- self._tool.executive.run_and_throw_if_fail(self._tool.port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root)
+ self._tool.executive.run_and_throw_if_fail(self._tool.deprecated_port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root)
except ScriptError, e:
if self._options.non_interactive:
# We need to re-raise the exception here to have the
diff --git a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py b/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py
index 191352440..a4cbe82c5 100644
--- a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py
+++ b/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py
@@ -28,12 +28,10 @@
from webkitpy.tool.steps.abstractstep import AbstractStep
from webkitpy.tool.steps.options import Options
+from webkitpy.common.system.executive import ScriptError
class CleanWorkingDirectory(AbstractStep):
- def __init__(self, tool, options, allow_local_commits=False):
- AbstractStep.__init__(self, tool, options)
- self._allow_local_commits = allow_local_commits
@classmethod
def options(cls):
@@ -45,6 +43,8 @@ class CleanWorkingDirectory(AbstractStep):
def run(self, state):
if not self._options.clean:
return
- if not self._allow_local_commits:
- self._tool.scm().ensure_no_local_commits(self._options.force_clean)
- self._tool.scm().ensure_clean_working_directory(force_clean=self._options.force_clean)
+
+ if self._tool.scm().has_working_directory_changes() and not self._options.force_clean:
+ raise ScriptError("Working directory has changes, pass --force-clean to continue.")
+
+ self._tool.scm().discard_working_directory_changes()
diff --git a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py b/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py
index 15a8850a5..7e31a9bd8 100644
--- a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py
@@ -26,27 +26,43 @@
# (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
+import unittest2 as unittest
from webkitpy.thirdparty.mock import Mock
from webkitpy.tool.mocktool import MockOptions, MockTool
from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory
+from webkitpy.common.system.executive import ScriptError
class CleanWorkingDirectoryTest(unittest.TestCase):
- def test_run(self):
+ def test_run_working_directory_changes_no_force(self):
tool = MockTool()
tool._scm = Mock()
- tool._scm.checkout_root = '/mock-checkout'
step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=False))
+ tool._scm.has_working_directory_changes = lambda: True
+ self.assertRaises(ScriptError, step.run, {})
+ self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 0)
+
+ def test_run_working_directory_changes_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=True))
+ tool._scm.has_working_directory_changes = lambda: True
+ step.run({})
+ self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 1)
+
+ def test_run_no_local_changes(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=False))
+ tool._scm.has_working_directory_changes = lambda: False
+ tool._scm.has_local_commits = lambda: False
step.run({})
- self.assertEqual(tool._scm.ensure_no_local_commits.call_count, 1)
- self.assertEqual(tool._scm.ensure_clean_working_directory.call_count, 1)
+ self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 1)
def test_no_clean(self):
tool = MockTool()
tool._scm = Mock()
step = CleanWorkingDirectory(tool, MockOptions(clean=False))
step.run({})
- self.assertEqual(tool._scm.ensure_no_local_commits.call_count, 0)
- self.assertEqual(tool._scm.ensure_clean_working_directory.call_count, 0)
+ self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 0)
diff --git a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py b/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py
deleted file mode 100644
index f06f94ef4..000000000
--- a/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2010 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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.
-
-from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory
-
-class CleanWorkingDirectoryWithLocalCommits(CleanWorkingDirectory):
- def __init__(self, tool, options):
- # FIXME: This a bit of a hack. Consider doing this more cleanly.
- CleanWorkingDirectory.__init__(self, tool, options, allow_local_commits=True)
diff --git a/Tools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py b/Tools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py
index 6969c4e9a..b042d4258 100644
--- a/Tools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
diff --git a/Tools/Scripts/webkitpy/tool/steps/commit.py b/Tools/Scripts/webkitpy/tool/steps/commit.py
index 2bffa4c2a..1d5109a00 100644
--- a/Tools/Scripts/webkitpy/tool/steps/commit.py
+++ b/Tools/Scripts/webkitpy/tool/steps/commit.py
@@ -40,20 +40,17 @@ _log = logging.getLogger(__name__)
class Commit(AbstractStep):
- # FIXME: This option exists only to make sure we don't break scripts which include --ignore-builders
- # You can safely delete this option any time after 11/01/11.
@classmethod
def options(cls):
return AbstractStep.options() + [
- Options.check_builders,
Options.non_interactive,
]
def _commit_warning(self, error):
- working_directory_message = "" if error.working_directory_is_clean else " and working copy changes"
- return ('There are %s local commits%s. Everything will be committed as a single commit. '
+ return ('There are %s local commits (and possibly changes in the working directory. '
+ 'Everything will be committed as a single commit. '
'To avoid this prompt, set "git config webkit-patch.commit-should-always-squash true".' % (
- error.num_local_commits, working_directory_message))
+ error.num_local_commits))
def _check_test_expectations(self, changed_files):
test_expectations_files = [filename for filename in changed_files if filename.endswith('TestExpectations')]
@@ -63,7 +60,7 @@ class Commit(AbstractStep):
args = ["--diff-files"]
args.extend(test_expectations_files)
try:
- self._tool.executive.run_and_throw_if_fail(self._tool.port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root)
+ self._tool.executive.run_and_throw_if_fail(self._tool.deprecated_port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root)
except ScriptError, e:
if self._options.non_interactive:
raise
@@ -76,12 +73,11 @@ class Commit(AbstractStep):
raise Exception("Attempted to commit with a commit message shorter than 10 characters. Either your patch is missing a ChangeLog or webkit-patch may have a bug.")
self._check_test_expectations(self._changed_files(state))
-
self._state = state
username = None
password = None
- force_squash = False
+ force_squash = self._options.non_interactive
num_tries = 0
while num_tries < 3:
@@ -95,7 +91,7 @@ class Commit(AbstractStep):
self._state["commit_text"] = commit_text
break;
except AmbiguousCommitError, e:
- if self._options.non_interactive or self._tool.user.confirm(self._commit_warning(e)):
+ if self._tool.user.confirm(self._commit_warning(e)):
force_squash = True
else:
# This will correctly interrupt the rest of the commit process.
diff --git a/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py b/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
index 936e3ebab..c6b76b428 100644
--- a/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.system.executive import ScriptError
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py b/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py
index 4bbd383ae..8a84cc702 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelogfordepsroll.py
+++ b/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Google Inc. All rights reserved.
+# Copyright (C) 2010 Google 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,13 +26,27 @@
# (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.common.checkout.changelog import ChangeLog
from webkitpy.tool.steps.abstractstep import AbstractStep
+from webkitpy.tool.steps.options import Options
+from webkitpy.common.system.executive import ScriptError
-class PrepareChangeLogForDEPSRoll(AbstractStep):
+class DiscardLocalChanges(AbstractStep):
+
+ @classmethod
+ def options(cls):
+ return AbstractStep.options() + [
+ Options.clean,
+ Options.force_clean,
+ ]
+
def run(self, state):
- self._tool.executive.run_and_throw_if_fail(self._tool.port().prepare_changelog_command())
- changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None)
- for changelog_path in changelog_paths:
- ChangeLog(changelog_path).update_with_unreviewed_message("Unreviewed. Rolled DEPS.\n\n")
+ if not self._options.clean:
+ return
+
+ if not self._options.force_clean:
+ if self._tool.scm().has_working_directory_changes():
+ raise ScriptError("Working directory has changes, pass --force-clean to continue.")
+ if self._tool.scm().has_local_commits():
+ raise ScriptError("Repository has local commits, pass --force-clean to continue.")
+ self._tool.scm().discard_local_changes()
diff --git a/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py b/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py
new file mode 100644
index 000000000..d38fc926c
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py
@@ -0,0 +1,97 @@
+# Copyright (C) 2010 Google 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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 unittest2 as unittest
+
+from webkitpy.thirdparty.mock import Mock
+from webkitpy.tool.mocktool import MockOptions, MockTool
+from webkitpy.tool.steps.discardlocalchanges import DiscardLocalChanges
+from webkitpy.common.system.executive import ScriptError
+
+
+class DiscardLocalChangesTest(unittest.TestCase):
+ def test_skip_on_clean(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ step = DiscardLocalChanges(tool, MockOptions(clean=False))
+ step.run({})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 0)
+
+ def test_working_changes_exist_with_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: True
+ tool._scm.has_local_commits = lambda: False
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True))
+ step.run({})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 1)
+
+ def test_local_commits_exist_with_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: False
+ tool._scm.has_local_commits = lambda: True
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True))
+ step.run({})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 1)
+
+ def test_local_commits_and_working_changes_exist_with_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: True
+ tool._scm.has_local_commits = lambda: True
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True))
+ step.run({})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 1)
+
+ def test_no_changes_exist_with_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: False
+ tool._scm.has_local_commits = lambda: False
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True))
+ step.run({})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 1)
+
+ def test_error_working_changes_exist_without_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: True
+ tool._scm.has_local_commits = lambda: False
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=False))
+ self.assertRaises(ScriptError, step.run, {})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 0)
+
+ def test_error_local_commits_exist_without_force(self):
+ tool = MockTool()
+ tool._scm = Mock()
+ tool._scm.has_working_directory_changes = lambda: False
+ tool._scm.has_local_commits = lambda: True
+ step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=False))
+ self.assertRaises(ScriptError, step.run, {})
+ self.assertEqual(tool._scm.discard_local_changes.call_count, 0)
diff --git a/Tools/Scripts/webkitpy/tool/steps/haslanded.py b/Tools/Scripts/webkitpy/tool/steps/haslanded.py
new file mode 100644
index 000000000..b0692b32b
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/steps/haslanded.py
@@ -0,0 +1,120 @@
+# Copyright (C) 2010 Google 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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 cStringIO as StringIO
+import logging
+import sys
+import re
+import tempfile
+
+from webkitpy.tool.steps.abstractstep import AbstractStep
+from webkitpy.common.system.executive import Executive, ScriptError
+from webkitpy.common.checkout import diff_parser
+
+from webkitpy.tool.steps import confirmdiff
+
+_log = logging.getLogger(__name__)
+
+
+class HasLanded(confirmdiff.ConfirmDiff):
+
+ @classmethod
+ def convert_to_svn(cls, diff):
+ lines = StringIO.StringIO(diff).readlines()
+ convert = diff_parser.get_diff_converter(lines)
+ return "".join(convert(x) for x in lines)
+
+ @classmethod
+ def strip_change_log(cls, diff):
+ output = []
+ skipping = False
+ for line in StringIO.StringIO(diff).readlines():
+ indexline = re.match("^Index: ([^\\n]*/)?([^/\\n]*)$", line)
+ if skipping and indexline:
+ skipping = False
+ if indexline and indexline.group(2) == "ChangeLog":
+ skipping = True
+ if not skipping:
+ output.append(line)
+ return "".join(output)
+
+ @classmethod
+ def diff_diff(cls, diff1, diff2, diff1_suffix, diff2_suffix, executive=None):
+ # Now this is where it gets complicated, we need to compare our diff to the diff at landed_revision.
+ diff1_patch = tempfile.NamedTemporaryFile(suffix=diff1_suffix + '.patch')
+ diff1_patch.write(diff1)
+ diff1_patch.flush()
+
+ # Check if there are any differences in the patch that don't happen
+ diff2_patch = tempfile.NamedTemporaryFile(suffix=diff2_suffix + '.patch')
+ diff2_patch.write(diff2)
+ diff2_patch.flush()
+
+ # Diff the two diff's together...
+ if not executive:
+ executive = Executive()
+
+ try:
+ return executive.run_command(
+ ["interdiff", diff1_patch.name, diff2_patch.name], decode_output=False)
+ except ScriptError, e:
+ _log.warning("Unable to find interdiff util (part of GNU difftools package) which is required.")
+ raise
+
+ def run(self, state):
+ # Check if there are changes first
+ if not self._tool.scm().local_changes_exist():
+ _log.warn("No local changes found, exiting.")
+ return True
+
+ # Check if there is a SVN revision in the bug from the commit queue
+ landed_revision = self.cached_lookup(state, "bug").commit_revision()
+ if not landed_revision:
+ raise ScriptError("Unable to find landed message in associated bug.")
+
+ # Now this is there it gets complicated, we need to compare our diff to the diff at landed_revision.
+ landed_diff_bin = self._tool.scm().diff_for_revision(landed_revision)
+ landed_diff_trimmed = self.strip_change_log(self.convert_to_svn(landed_diff_bin))
+
+ # Check if there are any differences in the patch that don't happen
+ local_diff_bin = self._tool.scm().create_patch()
+ local_diff_trimmed = self.strip_change_log(self.convert_to_svn(local_diff_bin))
+
+ # Diff the two diff's together...
+ diff_diff = self.diff_diff(landed_diff_trimmed, local_diff_trimmed,
+ '-landed', '-local',
+ executive=self._tool.executive)
+
+ with self._show_pretty_diff(diff_diff) as pretty_diff_file:
+ if not pretty_diff_file:
+ self._tool.user.page(diff_diff)
+
+ if self._tool.user.confirm("May I discard local changes?"):
+ # Discard changes if the user confirmed we should
+ _log.warn("Discarding changes as requested.")
+ self._tool.scm().discard_local_changes()
diff --git a/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py b/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py
new file mode 100644
index 000000000..3a67029a8
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py
@@ -0,0 +1,299 @@
+# Copyright (C) 2009 Google 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:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+# OWNER OR 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 unittest2 as unittest
+import subprocess
+
+from webkitpy.tool.steps.haslanded import HasLanded
+
+
+class HasLandedTest(unittest.TestCase):
+ maxDiff = None
+
+ @unittest.skipUnless(subprocess.call('which interdiff', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0, "requires interdiff")
+ def test_run(self):
+ # These patches require trailing whitespace to remain valid patches.
+ diff1 = """\
+Index: a.py
+===================================================================
+--- a.py
++++ a.py
+@@ -1,3 +1,5 @@
+ A
+ B
+ C
++D
++E
+Index: b.py
+===================================================================
+--- b.py 2013-01-21 15:20:59.693887185 +1100
++++ b.py 2013-01-21 15:22:24.382555711 +1100
+@@ -1,3 +1,5 @@
+ 1
+ 2
+ 3
++4
++5
+"""
+
+ diff1_add_line = """\
+Index: a.py
+===================================================================
+--- a.py
++++ a.py
+@@ -1,3 +1,6 @@
+ A
+ B
+ C
++D
++E
++F
+Index: b.py
+===================================================================
+--- b.py
++++ b.py
+@@ -1,3 +1,5 @@
+ 1
+ 2
+ 3
++4
++5
+"""
+
+ diff1_remove_line = """\
+Index: a.py
+===================================================================
+--- a.py
++++ a.py
+@@ -1,3 +1,4 @@
+ A
+ B
+ C
++D
+Index: b.py
+===================================================================
+--- b.py
++++ b.py
+@@ -1,3 +1,5 @@
+ 1
+ 2
+ 3
++4
++5
+"""
+
+ diff1_add_file = diff1 + """\
+Index: c.py
+===================================================================
+--- c.py
++++ c.py
+@@ -1,3 +1,5 @@
+ 1
+ 2
+ 3
++4
++5
+"""
+
+ diff1_remove_file = """\
+Index: a.py
+===================================================================
+--- a.py
++++ a.py
+@@ -1,3 +1,5 @@
+ A
+ B
+ C
++D
++E
+"""
+ self.assertMultiLineEqual(
+ HasLanded.diff_diff(diff1, diff1_add_line, '', 'add-line'),
+ """\
+diff -u a.py a.py
+--- a.py
++++ a.py
+@@ -5,0 +6 @@
++F
+""")
+
+ self.assertMultiLineEqual(
+ HasLanded.diff_diff(diff1, diff1_remove_line, '', 'remove-line'),
+ """\
+diff -u a.py a.py
+--- a.py
++++ a.py
+@@ -5 +4,0 @@
+-E
+""")
+ self.assertMultiLineEqual(
+ HasLanded.diff_diff(diff1, diff1_add_file, '', 'add-file'),
+ """\
+only in patch2:
+unchanged:
+--- c.py
++++ c.py
+@@ -1,3 +1,5 @@
+ 1
+ 2
+ 3
++4
++5
+""")
+ self.assertMultiLineEqual(
+ HasLanded.diff_diff(diff1, diff1_remove_file, '', 'remove-file'),
+ """\
+reverted:
+--- b.py 2013-01-21 15:22:24.382555711 +1100
++++ b.py 2013-01-21 15:20:59.693887185 +1100
+@@ -1,5 +1,3 @@
+ 1
+ 2
+ 3
+-4
+-5
+""")
+
+ def test_convert_to_svn_and_strip_change_log(self):
+ # These patches require trailing whitespace to remain valid patches.
+ testbefore1 = HasLanded.convert_to_svn("""\
+diff --git a/Tools/ChangeLog b/Tools/ChangeLog
+index 219ba72..0390b73 100644
+--- a/Tools/ChangeLog
++++ b/Tools/ChangeLog
+@@ -1,3 +1,32 @@
++2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com>
++
++ Adding "has-landed" command to webkit-patch which allows a person to
++ Reviewed by NOBODY (OOPS!).
++
+ 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com>
+
+ Extend diff_parser to support the --full-index output.
+diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+index 4bf8ec6..3a128cb 100644
+--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
++++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+""")
+ testafter1 = HasLanded.convert_to_svn("""\
+diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+index 4bf8ec6..3a128cb 100644
+--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
++++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+diff --git a/Tools/ChangeLog b/Tools/ChangeLog
+index 219ba72..0390b73 100644
+--- a/Tools/ChangeLog
++++ b/Tools/ChangeLog
+@@ -1,3 +1,32 @@
++2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com>
++
++ Adding "has-landed" command to webkit-patch which allows a person to
++ Reviewed by NOBODY (OOPS!).
++
+ 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com>
+
+ Extend diff_parser to support the --full-index output.
+""")
+ testexpected1 = """\
+Index: Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+===================================================================
+--- Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
++++ Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+"""
+ testmiddle1 = HasLanded.convert_to_svn("""\
+diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+index 4bf8ec6..3a128cb 100644
+--- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
++++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+diff --git a/ChangeLog b/ChangeLog
+index 219ba72..0390b73 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,32 @@
++2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com>
++
++ Adding "has-landed" command to webkit-patch which allows a person to
++ Reviewed by NOBODY (OOPS!).
++
+ 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com>
+
+ Extend diff_parser to support the --full-index output.
+diff --git a/Tools/Scripts/webkitpy/common/other.py b/Tools/Scripts/webkitpy/common/other.py
+index 4bf8ec6..3a128cb 100644
+--- a/Tools/Scripts/webkitpy/common/other.py
++++ b/Tools/Scripts/webkitpy/common/other.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+""")
+ testexpected2 = """\
+Index: Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+===================================================================
+--- Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
++++ Tools/Scripts/webkitpy/common/net/bugzilla/bug.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+Index: Tools/Scripts/webkitpy/common/other.py
+===================================================================
+--- Tools/Scripts/webkitpy/common/other.py
++++ Tools/Scripts/webkitpy/common/other.py
+@@ -28,6 +28,8 @@
++import re
++
+ from .attachment import Attachment
+
+"""
+
+ self.assertMultiLineEqual(testexpected1, HasLanded.strip_change_log(testbefore1))
+ self.assertMultiLineEqual(testexpected1, HasLanded.strip_change_log(testafter1))
+ self.assertMultiLineEqual(testexpected2, HasLanded.strip_change_log(testmiddle1))
diff --git a/Tools/Scripts/webkitpy/tool/steps/options.py b/Tools/Scripts/webkitpy/tool/steps/options.py
index c29e59d9c..7eda61459 100644
--- a/Tools/Scripts/webkitpy/tool/steps/options.py
+++ b/Tools/Scripts/webkitpy/tool/steps/options.py
@@ -33,7 +33,6 @@ class Options(object):
build = make_option("--build", action="store_true", dest="build", default=False, help="Build and run run-webkit-tests before committing.")
build_style = make_option("--build-style", action="store", dest="build_style", default=None, help="Whether to build debug, release, or both.")
cc = make_option("--cc", action="store", type="string", dest="cc", help="Comma-separated list of email addresses to carbon-copy.")
- check_builders = make_option("--ignore-builders", action="store_false", dest="check_builders", default=True, help="DEPRECATED: Will be removed any time after 11/01/11.")
check_style = make_option("--ignore-style", action="store_false", dest="check_style", default=True, help="Don't check to see if the patch has proper style before uploading.")
check_style_filter = make_option("--check-style-filter", action="store", type="string", dest="check_style_filter", default=None, help="Filter style-checker rules (see check-webkit-style --help).")
clean = make_option("--no-clean", action="store_false", dest="clean", default=True, help="Don't check if the working directory is clean before applying patches")
@@ -57,4 +56,5 @@ class Options(object):
suggest_reviewers = make_option("--suggest-reviewers", action="store_true", default=False, help="Offer to CC appropriate reviewers.")
test = make_option("--test", action="store_true", dest="test", default=False, help="Run run-webkit-tests before committing.")
update = make_option("--no-update", action="store_false", dest="update", default=True, help="Don't update the working directory.")
+ update_changelogs = make_option("--update-changelogs", action="store_true", dest="update_changelogs", default=False, help="Update existing ChangeLog entries with new date, bug description, and touched files/functions.")
changelog_count = make_option("--changelog-count", action="store", type="int", dest="changelog_count", help="Number of changelogs to parse.")
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
index 4d80ab61f..716ab826d 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
@@ -27,6 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
+import re
import sys
from webkitpy.common.checkout.changelog import ChangeLog
@@ -44,6 +45,7 @@ class PrepareChangeLog(AbstractStep):
Options.quiet,
Options.email,
Options.git_commit,
+ Options.update_changelogs,
]
def _ensure_bug_url(self, state):
@@ -52,17 +54,60 @@ class PrepareChangeLog(AbstractStep):
bug_id = state.get("bug_id")
changelogs = self.cached_lookup(state, "changelogs")
for changelog_path in changelogs:
- changelog = ChangeLog(changelog_path)
+ changelog = ChangeLog(changelog_path, self._tool.filesystem)
if not changelog.latest_entry().bug_id():
changelog.set_short_description_and_bug_url(
self.cached_lookup(state, "bug_title"),
self._tool.bugs.bug_url_for_bug_id(bug_id))
+ def _resolve_existing_entry(self, changelog_path):
+ # When this is called, the top entry in the ChangeLog was just created
+ # by prepare-ChangeLog, as an clean updated version of the one below it.
+ with self._tool.filesystem.open_text_file_for_reading(changelog_path) as changelog_file:
+ entries_gen = ChangeLog.parse_entries_from_file(changelog_file)
+ entries = zip(entries_gen, range(2))
+
+ if not len(entries):
+ raise Exception("Expected to find at least two ChangeLog entries in %s but found none." % changelog_path)
+ if len(entries) == 1:
+ # If we get here, it probably means we've just rolled over to a
+ # new CL file, so we don't have anything to resolve.
+ return
+
+ (new_entry, _), (old_entry, _) = entries
+ final_entry = self._merge_entries(old_entry, new_entry)
+
+ changelog = ChangeLog(changelog_path, self._tool.filesystem)
+ changelog.delete_entries(2)
+ changelog.prepend_text(final_entry)
+
+ def _merge_entries(self, old_entry, new_entry):
+ final_entry = old_entry.contents()
+
+ final_entry = final_entry.replace(old_entry.date(), new_entry.date(), 1)
+
+ new_bug_desc = new_entry.bug_description()
+ old_bug_desc = old_entry.bug_description()
+ if new_bug_desc and old_bug_desc and new_bug_desc != old_bug_desc:
+ final_entry = final_entry.replace(old_bug_desc, new_bug_desc)
+
+ new_touched = new_entry.touched_functions()
+ old_touched = old_entry.touched_functions()
+ if new_touched != old_touched:
+ if old_entry.is_touched_files_text_clean():
+ final_entry = final_entry.replace(old_entry.touched_files_text(), new_entry.touched_files_text())
+ else:
+ final_entry += "\n" + new_entry.touched_files_text()
+
+ return final_entry + "\n"
+
def run(self, state):
if self.cached_lookup(state, "changelogs"):
self._ensure_bug_url(state)
- return
- args = self._tool.port().prepare_changelog_command()
+ if not self._options.update_changelogs:
+ return
+
+ args = self._tool.deprecated_port().prepare_changelog_command()
if state.get("bug_id"):
args.append("--bug=%s" % state["bug_id"])
args.append("--description=%s" % self.cached_lookup(state, 'bug_title'))
@@ -75,8 +120,15 @@ class PrepareChangeLog(AbstractStep):
args.extend(self._changed_files(state))
try:
- self._tool.executive.run_and_throw_if_fail(args, self._options.quiet, cwd=self._tool.scm().checkout_root)
+ output = self._tool.executive.run_and_throw_if_fail(args, self._options.quiet, cwd=self._tool.scm().checkout_root)
except ScriptError, e:
_log.error("Unable to prepare ChangeLogs.")
sys.exit(1)
+
+ # These are the ChangeLog entries added by prepare-Changelog
+ changelogs = re.findall(r'Editing the (\S*/ChangeLog) file.', output)
+ changelogs = set(self._tool.filesystem.join(self._tool.scm().checkout_root, f) for f in changelogs)
+ for changelog in changelogs & set(self.cached_lookup(state, "changelogs")):
+ self._resolve_existing_entry(changelog)
+
self.did_modify_checkout(state)
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
index fc31d1fa9..803f072a3 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
@@ -26,32 +26,108 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import os
-import unittest
+import unittest2 as unittest
# Do not import changelog_unittest.ChangeLogTest directly as that will cause it to be run again.
from webkitpy.common.checkout import changelog_unittest
+from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
from webkitpy.tool.steps.preparechangelog import PrepareChangeLog
-
class PrepareChangeLogTest(changelog_unittest.ChangeLogTest):
+ def test_resolve_existing_entry(self):
+ step = PrepareChangeLog(MockTool(), MockOptions())
+
+ headers = ["2013-01-18 Timothy Loh <timloh@chromium.com>\n\n",
+ "2013-01-20 Timothy Loh <timloh@chromium.com>\n\n",
+ u"2009-08-17 Tor Arne Vestb\xf8 <vestbo@webkit.org>\n\n",
+ u"2009-08-18 Tor Arne Vestb\xf8 <vestbo@webkit.org>\n\n",
+ "2013-01-18 Eric Seidel <eric@webkit.org>\n\n",
+ "2013-01-20 Eric Seidel <eric@webkit.org>\n\n",
+ ]
+
+ bug_descs = [" prepare-Changelog should support updating the list of changed files\n",
+ " webkit-patch upload should support updating the list of changed files\n"]
+
+ bug_url = " https://bugs.webkit.org/show_bug.cgi?id=74358\n\n"
+
+ descriptions = ["", " A description of the changes.\n\n",
+ " A description.\n\n With some\n line breaks\n\n"]
+
+ changes = [
+""" * Scripts/webkitpy/tool/steps/preparechangelog.py:
+ (PrepareChangeLog):
+ (PrepareChangeLog.run):\n\n""",
+""" * Scripts/webkitpy/tool/steps/preparechangelog.py:
+ (PrepareChangeLog._resolve_existing_entry):
+ (PrepareChangeLog):
+ (PrepareChangeLog.run):\n\n""",
+""" * Scripts/webkitpy/tool/steps/preparechangelog.py:
+ (PrepareChangeLog): Some annotations
+ (PrepareChangeLog.run):
+ More annotations\n\n""",
+""" * Scripts/webkitpy/tool/steps/preparechangelog.py:
+ (PrepareChangeLog): Some annotations
+ (PrepareChangeLog.run):
+ More annotations
+
+ * Scripts/webkitpy/tool/steps/preparechangelog.py:
+ (PrepareChangeLog._resolve_existing_entry):
+ (PrepareChangeLog):
+ (PrepareChangeLog.run):\n\n""",
+ ]
+
+ def make_entry(indices):
+ a, b, c, d = indices
+ return headers[a] + bug_descs[b] + bug_url + descriptions[c] + changes[d]
+
+ test_cases = [((0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0)),
+ ((0, 0, 0, 0), (0, 0, 1, 0), (0, 0, 1, 0)),
+ ((1, 0, 0, 0), (0, 0, 2, 0), (1, 0, 2, 0)),
+ ((0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 1, 0)),
+ ((0, 0, 0, 1), (0, 0, 0, 0), (0, 0, 0, 1)),
+ ((0, 0, 0, 0), (0, 0, 1, 1), (0, 0, 1, 0)),
+ ((0, 0, 0, 0), (0, 0, 2, 2), (0, 0, 2, 2)),
+ ((0, 0, 0, 1), (0, 0, 1, 2), (0, 0, 1, 3)),
+ ((1, 1, 0, 1), (0, 0, 0, 2), (1, 1, 0, 3)),
+ ((3, 0, 0, 0), (2, 0, 1, 0), (3, 0, 1, 0)),
+ ((4, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0)),
+ ((5, 0, 0, 0), (0, 0, 0, 0), (1, 0, 0, 0)),
+ ((0, 0, 0, 0), (4, 0, 0, 0), (4, 0, 0, 0)),
+ ((1, 0, 0, 0), (4, 0, 0, 0), (5, 0, 0, 0)),
+ ]
+
+ for new, old, final in test_cases:
+ new_entry = make_entry(new)
+ old_entry = make_entry(old)
+ start_file = new_entry + old_entry + self._rolled_over_footer
+
+ final_entry = make_entry(final)
+ end_file = final_entry + self._rolled_over_footer
+
+ path = "ChangeLog"
+ step._tool.filesystem = MockFileSystem()
+ step._tool.filesystem.write_text_file(path, start_file)
+ step._resolve_existing_entry(path)
+ actual_output = step._tool.filesystem.read_text_file(path)
+ self.assertEquals(actual_output, end_file)
+
def test_ensure_bug_url(self):
- # FIXME: This should use a MockFileSystem instead of a real FileSystem.
capture = OutputCapture()
step = PrepareChangeLog(MockTool(), MockOptions())
changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
- changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
+ changelog_path = "ChangeLog"
state = {
"bug_title": "Example title",
"bug_id": 1234,
"changelogs": [changelog_path],
}
- capture.assert_outputs(self, step.run, [state])
- actual_contents = self._read_file_contents(changelog_path, "utf-8")
+ step._tool.filesystem = MockFileSystem()
+ step._tool.filesystem.write_text_file(changelog_path, changelog_contents)
+ capture.assert_outputs(self, step._ensure_bug_url, [state])
+ actual_contents = step._tool.filesystem.read_text_file(changelog_path)
expected_message = "Example title\n http://example.com/1234"
expected_contents = changelog_contents.replace("Need a short description (OOPS!).\n Need the bug URL (OOPS!).", expected_message)
- os.remove(changelog_path)
- self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
+ self.assertEqual(actual_contents, expected_contents)
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py
index 95a99c320..82e7b0252 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py
@@ -48,7 +48,7 @@ class PrepareChangeLogForRevert(AbstractStep):
def run(self, state):
# This could move to prepare-ChangeLog by adding a --revert= option.
- self._tool.executive.run_and_throw_if_fail(self._tool.port().prepare_changelog_command(), cwd=self._tool.scm().checkout_root)
+ self._tool.executive.run_and_throw_if_fail(self._tool.deprecated_port().prepare_changelog_command(), cwd=self._tool.scm().checkout_root)
changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None)
bug_url = self._tool.bugs.bug_url_for_bug_id(state["bug_id"]) if state["bug_id"] else None
message = self._message_for_revert(state["revision_list"], state["reason"], bug_url)
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
index b82cb4aa2..3ec6e9a60 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
@@ -26,27 +26,17 @@
# (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 codecs
-import os
-import tempfile
-import unittest
+import unittest2 as unittest
# Do not import changelog_unittest.ChangeLogTest directly as that will cause it to be run again.
from webkitpy.common.checkout import changelog_unittest
from webkitpy.common.checkout.changelog import ChangeLog
+from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.tool.steps.preparechangelogforrevert import *
class UpdateChangeLogsForRevertTest(unittest.TestCase):
- @staticmethod
- def _write_tmp_file_with_contents(byte_array):
- assert(isinstance(byte_array, str))
- (file_descriptor, file_path) = tempfile.mkstemp() # NamedTemporaryFile always deletes the file on close in python < 2.6
- with os.fdopen(file_descriptor, "w") as file:
- file.write(byte_array)
- return file_path
-
_revert_entry_with_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org>
Unreviewed, rolling out r12345.
@@ -110,13 +100,13 @@ class UpdateChangeLogsForRevertTest(unittest.TestCase):
def _assert_message_for_revert_output(self, args, expected_entry):
changelog_contents = u"%s\n%s" % (changelog_unittest.ChangeLogTest._new_entry_boilerplate, changelog_unittest.ChangeLogTest._example_changelog)
- changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
- changelog = ChangeLog(changelog_path)
+ changelog_path = "ChangeLog"
+ fs = MockFileSystem({changelog_path: changelog_contents.encode("utf-8")})
+ changelog = ChangeLog(changelog_path, fs)
changelog.update_with_unreviewed_message(PrepareChangeLogForRevert._message_for_revert(*args))
actual_entry = changelog.latest_entry()
- os.remove(changelog_path)
- self.assertEqual(actual_entry.contents(), expected_entry)
- self.assertEqual(actual_entry.reviewer_text(), None)
+ self.assertMultiLineEqual(actual_entry.contents(), expected_entry)
+ self.assertIsNone(actual_entry.reviewer_text())
# These checks could be removed to allow this to work on other entries:
self.assertEqual(actual_entry.author_name(), "Eric Seidel")
self.assertEqual(actual_entry.author_email(), "eric@webkit.org")
diff --git a/Tools/Scripts/webkitpy/tool/steps/runtests.py b/Tools/Scripts/webkitpy/tool/steps/runtests.py
index 6dc90f92c..a45628b2d 100644
--- a/Tools/Scripts/webkitpy/tool/steps/runtests.py
+++ b/Tools/Scripts/webkitpy/tool/steps/runtests.py
@@ -1,9 +1,9 @@
# Copyright (C) 2010 Google 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:
-#
+#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
@@ -13,7 +13,7 @@
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
-#
+#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
@@ -27,7 +27,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
-
+import os
+import platform
+import sys
from webkitpy.tool.steps.abstractstep import AbstractStep
from webkitpy.tool.steps.options import Options
from webkitpy.common.system.executive import ScriptError
@@ -41,6 +43,7 @@ class RunTests(AbstractStep):
@classmethod
def options(cls):
return AbstractStep.options() + [
+ Options.build_style,
Options.test,
Options.non_interactive,
Options.quiet,
@@ -53,44 +56,59 @@ class RunTests(AbstractStep):
if not self._options.non_interactive:
# FIXME: We should teach the commit-queue and the EWS how to run these tests.
- python_unittests_command = self._tool.port().run_python_unittests_command()
+ python_unittests_command = self._tool.deprecated_port().run_python_unittests_command()
if python_unittests_command:
_log.info("Running Python unit tests")
self._tool.executive.run_and_throw_if_fail(python_unittests_command, cwd=self._tool.scm().checkout_root)
- perl_unittests_command = self._tool.port().run_perl_unittests_command()
+ perl_unittests_command = self._tool.deprecated_port().run_perl_unittests_command()
if perl_unittests_command:
_log.info("Running Perl unit tests")
self._tool.executive.run_and_throw_if_fail(perl_unittests_command, cwd=self._tool.scm().checkout_root)
- javascriptcore_tests_command = self._tool.port().run_javascriptcore_tests_command()
+ javascriptcore_tests_command = self._tool.deprecated_port().run_javascriptcore_tests_command()
if javascriptcore_tests_command:
_log.info("Running JavaScriptCore tests")
self._tool.executive.run_and_throw_if_fail(javascriptcore_tests_command, quiet=True, cwd=self._tool.scm().checkout_root)
- webkit_unit_tests_command = self._tool.port().run_webkit_unit_tests_command()
+ bindings_tests_command = self._tool.deprecated_port().run_bindings_tests_command()
+ if bindings_tests_command:
+ _log.info("Running bindings generation tests")
+ args = bindings_tests_command
+ try:
+ self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root)
+ except ScriptError, e:
+ _log.info("Error running run-bindings-tests: %s" % e.message_with_output())
+
+ webkit_unit_tests_command = self._tool.deprecated_port().run_webkit_unit_tests_command()
if webkit_unit_tests_command:
_log.info("Running WebKit unit tests")
args = webkit_unit_tests_command
- if self._options.non_interactive:
- args.append("--gtest_output=xml:%s/webkit_unit_tests_output.xml" % self._tool.port().results_directory)
try:
self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root)
except ScriptError, e:
_log.info("Error running webkit_unit_tests: %s" % e.message_with_output())
+
_log.info("Running run-webkit-tests")
- args = self._tool.port().run_webkit_tests_command()
+ args = self._tool.deprecated_port().run_webkit_tests_command()
if self._options.non_interactive:
args.extend([
"--no-new-test-results",
- "--no-launch-safari",
- "--skip-failing-tests",
+ "--no-show-results",
"--exit-after-n-failures=%s" % self.NON_INTERACTIVE_FAILURE_LIMIT_COUNT,
- "--results-directory=%s" % self._tool.port().results_directory,
- "--quiet",
])
+ # old-run-webkit-tests does not support --skip-failing-tests
+ # Using --quiet one Windows fails when we try to use /dev/null, disabling for now until we find a fix
+ if sys.platform != "cygwin":
+ args.append("--quiet")
+ args.append("--skip-failing-tests")
+ else:
+ args.append("--no-build");
+
if self._options.quiet:
args.append("--quiet")
+
self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root)
+
diff --git a/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py b/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py
index 78a867b36..ef8920e9b 100644
--- a/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py
@@ -26,7 +26,9 @@
# (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
+import platform
+import sys
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
@@ -38,9 +40,22 @@ class RunTestsTest(unittest.TestCase):
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))
- expected_logs = """Running WebKit unit tests
-MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests', '--gtest_output=xml:/mock-results/webkit_unit_tests_output.xml'], cwd=/mock-checkout
+
+ if sys.platform != "cygwin":
+ expected_logs = """Running bindings generation tests
+MOCK run_and_throw_if_fail: ['mock-run-bindings-tests'], cwd=/mock-checkout
+Running WebKit unit tests
+MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests'], cwd=/mock-checkout
+Running run-webkit-tests
+MOCK run_and_throw_if_fail: ['mock-run-webkit-tests', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures=30', '--quiet', '--skip-failing-tests'], cwd=/mock-checkout
+"""
+ else:
+ expected_logs = """Running bindings generation tests
+MOCK run_and_throw_if_fail: ['mock-run-bindings-tests'], cwd=/mock-checkout
+Running WebKit unit tests
+MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests'], cwd=/mock-checkout
Running run-webkit-tests
-MOCK run_and_throw_if_fail: ['mock-run-webkit-tests', '--no-new-test-results', '--no-launch-safari', '--skip-failing-tests', '--exit-after-n-failures=30', '--results-directory=/mock-results', '--quiet'], cwd=/mock-checkout
+MOCK run_and_throw_if_fail: ['mock-run-webkit-tests', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures=30', '--no-build'], cwd=/mock-checkout
"""
+
OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs)
diff --git a/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py b/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py
index c4ea47b4d..7172ba7f5 100644
--- a/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.config.ports import DeprecatedPort
@@ -99,10 +99,9 @@ class StepsTest(unittest.TestCase):
mock_options = self._step_options()
mock_options.non_interactive = False
step = steps.RunTests(MockTool(log_executive=True), mock_options)
- # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment.
- mock_port = DeprecatedPort()
tool = MockTool(log_executive=True)
- tool.port = lambda: mock_port
+ # 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 = """Running Python unit tests
MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitpy'], cwd=/mock-checkout
@@ -110,6 +109,8 @@ 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
+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', '--quiet'], cwd=/mock-checkout
"""
diff --git a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers.py b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers.py
index 76bef35ac..40a24829b 100644
--- a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers.py
+++ b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers.py
@@ -42,9 +42,12 @@ class SuggestReviewers(AbstractStep):
if not self._options.suggest_reviewers:
return
- reviewers = self._tool.checkout().suggested_reviewers(self._options.git_commit, self._changed_files(state))
+ reviewers = self._tool.checkout().suggested_reviewers(self._options.git_commit, self._changed_files(state))[:5]
print "The following reviewers have recently modified files in your patch:"
- print "\n".join([reviewer.full_name for reviewer in reviewers])
+ print ", ".join([reviewer.full_name for reviewer in reviewers])
+
+ if not state.get('bug_id'):
+ return
if not self._tool.user.confirm("Would you like to CC them?"):
return
reviewer_emails = [reviewer.bugzilla_email() for reviewer in reviewers]
diff --git a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py
index 42254c86b..fc096f118 100644
--- a/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
diff --git a/Tools/Scripts/webkitpy/tool/steps/update.py b/Tools/Scripts/webkitpy/tool/steps/update.py
index 0737ebcd0..f70354078 100644
--- a/Tools/Scripts/webkitpy/tool/steps/update.py
+++ b/Tools/Scripts/webkitpy/tool/steps/update.py
@@ -50,5 +50,5 @@ class Update(AbstractStep):
self._tool.executive.run_and_throw_if_fail(self._update_command(), quiet=self._options.quiet, cwd=self._tool.scm().checkout_root)
def _update_command(self):
- update_command = self._tool.port().update_webkit_command(self._options.non_interactive)
+ update_command = self._tool.deprecated_port().update_webkit_command(self._options.non_interactive)
return update_command
diff --git a/Tools/Scripts/webkitpy/tool/steps/update_unittest.py b/Tools/Scripts/webkitpy/tool/steps/update_unittest.py
index c1a934db5..49d6b4098 100644
--- a/Tools/Scripts/webkitpy/tool/steps/update_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/update_unittest.py
@@ -26,9 +26,9 @@
# (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
+import unittest2 as unittest
-from webkitpy.common.config.ports import ChromiumPort, ChromiumAndroidPort, ChromiumXVFBPort
+from webkitpy.common.config.ports import MacPort, MacWK2Port
from webkitpy.tool.mocktool import MockOptions, MockTool
from webkitpy.tool.steps.update import Update
@@ -41,14 +41,11 @@ class UpdateTest(unittest.TestCase):
step = Update(tool, options)
self.assertEqual(["mock-update-webkit"], step._update_command())
- tool._deprecated_port = ChromiumPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium", "--force-update"], step._update_command())
+ tool._deprecated_port = MacPort()
+ self.assertEqual(["Tools/Scripts/update-webkit"], step._update_command())
- tool._deprecated_port = ChromiumXVFBPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium", "--force-update"], step._update_command())
-
- tool._deprecated_port = ChromiumAndroidPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium", "--force-update", "--chromium-android"], step._update_command())
+ tool._deprecated_port = MacWK2Port()
+ self.assertEqual(["Tools/Scripts/update-webkit"], step._update_command())
def test_update_command_interactive(self):
tool = MockTool()
@@ -56,11 +53,8 @@ class UpdateTest(unittest.TestCase):
step = Update(tool, options)
self.assertEqual(["mock-update-webkit"], step._update_command())
- tool._deprecated_port = ChromiumPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium"], step._update_command())
-
- tool._deprecated_port = ChromiumXVFBPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium"], step._update_command())
+ tool._deprecated_port = MacPort()
+ self.assertEqual(["Tools/Scripts/update-webkit"], step._update_command())
- tool._deprecated_port = ChromiumAndroidPort()
- self.assertEqual(["Tools/Scripts/update-webkit", "--chromium", "--chromium-android"], step._update_command())
+ tool._deprecated_port = MacWK2Port()
+ self.assertEqual(["Tools/Scripts/update-webkit"], step._update_command())
diff --git a/Tools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py b/Tools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py
index 3182cf3ab..d433e3f21 100644
--- a/Tools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.tool.mocktool import MockOptions, MockTool
diff --git a/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py b/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py
deleted file mode 100644
index 23d861bfc..000000000
--- a/Tools/Scripts/webkitpy/tool/steps/updatechromiumdeps.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright (C) 2011 Google 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:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
-# OWNER OR 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 sys
-import urllib2
-
-from webkitpy.tool.steps.abstractstep import AbstractStep
-from webkitpy.tool.steps.options import Options
-from webkitpy.common.config import urls
-
-_log = logging.getLogger(__name__)
-
-
-class UpdateChromiumDEPS(AbstractStep):
- @classmethod
- def options(cls):
- return AbstractStep.options() + [
- Options.non_interactive,
- ]
-
- # Notice that this method throws lots of exciting exceptions!
- def _fetch_last_known_good_revision(self):
- return int(urllib2.urlopen(urls.chromium_lkgr_url).read())
-
- def _validate_revisions(self, current_chromium_revision, new_chromium_revision):
- if new_chromium_revision < current_chromium_revision:
- message = "Current Chromium DEPS revision %s is newer than %s." % (current_chromium_revision, new_chromium_revision)
- if self._options.non_interactive:
- _log.error(message)
- sys.exit(1)
- _log.info(message)
- new_chromium_revision = self._tool.user.prompt("Enter new chromium revision (enter nothing to cancel):\n")
- try:
- new_chromium_revision = int(new_chromium_revision)
- except ValueError, TypeError:
- new_chromium_revision = None
- if not new_chromium_revision:
- _log.error("Unable to update Chromium DEPS")
- sys.exit(1)
-
- def run(self, state):
- # Note that state["chromium_revision"] must be defined, but can be None.
- new_chromium_revision = state["chromium_revision"]
- if not new_chromium_revision:
- new_chromium_revision = self._fetch_last_known_good_revision()
-
- deps = self._tool.checkout().chromium_deps()
- current_chromium_revision = deps.read_variable("chromium_rev")
- self._validate_revisions(current_chromium_revision, new_chromium_revision)
- _log.info("Updating Chromium DEPS to %s" % new_chromium_revision)
- deps.write_variable("chromium_rev", new_chromium_revision)
diff --git a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs.py b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs.py
index 061baa5ec..e77e5c01e 100644
--- a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs.py
+++ b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs.py
@@ -29,6 +29,7 @@
import logging
import sys
+from optparse import make_option
from webkitpy.tool.steps.abstractstep import AbstractStep
from webkitpy.tool.steps.options import Options
from webkitpy.common.checkout.diff_parser import DiffParser
@@ -42,12 +43,11 @@ class ValidateChangeLogs(AbstractStep):
@classmethod
def options(cls):
return AbstractStep.options() + [
+ make_option("--check-oops", action="store_true", default=False, help="Check there are no OOPS left in change log"),
Options.non_interactive,
]
def _check_changelog_diff(self, diff_file):
- if not self._tool.checkout().is_path_to_changelog(diff_file.filename):
- return True
# Each line is a tuple, the first value is the deleted line number
# Date, reviewer, bug title, bug url, and empty lines could all be
# identical in the most recent entries. If the diff starts any
@@ -64,6 +64,12 @@ class ValidateChangeLogs(AbstractStep):
return True
return False
+ def _changelog_contains_oops(self, diff_file):
+ for diff_line in diff_file.lines:
+ if 'OOPS!' in diff_line[2]:
+ return True
+ return False
+
def run(self, state):
changed_files = self.cached_lookup(state, "changed_files")
for filename in changed_files:
@@ -76,6 +82,11 @@ class ValidateChangeLogs(AbstractStep):
diff = self._tool.scm().diff_for_file(filename)
parsed_diff = DiffParser(diff.splitlines())
for filename, diff_file in parsed_diff.files.items():
+ if not self._tool.checkout().is_path_to_changelog(diff_file.filename):
+ continue
if not self._check_changelog_diff(diff_file):
_log.error("ChangeLog entry in %s is not at the top of the file." % diff_file.filename)
sys.exit(1)
+ if self._options.check_oops and self._changelog_contains_oops(diff_file):
+ _log.error("ChangeLog entry in %s contains OOPS!." % diff_file.filename)
+ sys.exit(1)
diff --git a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py
index c3b723ed1..50ecc4646 100644
--- a/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/validatechangelogs_unittest.py
@@ -26,7 +26,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.
-import unittest
+import unittest2 as unittest
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.thirdparty.mock import Mock
@@ -38,7 +38,6 @@ class ValidateChangeLogsTest(unittest.TestCase):
def _assert_start_line_produces_output(self, start_line, should_fail=False, non_interactive=False):
tool = MockTool()
- tool._checkout.is_path_to_changelog = lambda path: True
step = ValidateChangeLogs(tool, MockOptions(git_commit=None, non_interactive=non_interactive))
diff_file = Mock()
diff_file.filename = "mock/ChangeLog"
@@ -56,3 +55,15 @@ class ValidateChangeLogsTest(unittest.TestCase):
self._assert_start_line_produces_output(1, non_interactive=False)
self._assert_start_line_produces_output(8, non_interactive=True, should_fail=True)
+
+ def test_changelog_contains_oops(self):
+ tool = MockTool()
+ tool._checkout.is_path_to_changelog = lambda path: True
+ step = ValidateChangeLogs(tool, MockOptions(git_commit=None, non_interactive=True, check_oops=True))
+ diff_file = Mock()
+ diff_file.filename = "mock/ChangeLog"
+ diff_file.lines = [(1, 1, "foo"), (2, 2, "bar OOPS! bar"), (3, 3, "foo")]
+ self.assertTrue(OutputCapture().assert_outputs(self, step._changelog_contains_oops, [diff_file], expected_logs=''))
+
+ diff_file.lines = [(1, 1, "foo"), (2, 2, "bar OOPS bar"), (3, 3, "foo")]
+ self.assertFalse(OutputCapture().assert_outputs(self, step._changelog_contains_oops, [diff_file], expected_logs=''))