The parsing of change descriptions had a lot of overlap and inconsistencies between gcl and git-cl. In particular, we weren't handling TBR= consistently, or probably a few other things.
This change moves most of the code into presubmit_support and gclient_utils and just leaves the formatting differences for the messages between the two tools.
Review URL: http://codereview.chromium.org/6719004
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@79002 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gcl.py b/gcl.py
index 20762d1..70d8c74 100755
--- a/gcl.py
+++ b/gcl.py
@@ -290,9 +290,7 @@
self.name = name
self.issue = int(issue)
self.patchset = int(patchset)
- self._description = None
- self._subject = None
- self._reviewers = None
+ self._change_desc = None
self._set_description(description)
if files is None:
files = []
@@ -306,42 +304,21 @@
self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
def _get_description(self):
- return self._description
+ return self._change_desc.description
def _set_description(self, description):
- # TODO(dpranke): Cloned from git_cl.py. These should be shared.
- if not description:
- self._description = description
- return
-
- parsed_lines = []
- reviewers_re = re.compile(REVIEWERS_REGEX)
- reviewers = ''
- subject = ''
- for l in description.splitlines():
- if not subject:
- subject = l
- matched_reviewers = reviewers_re.match(l)
- if matched_reviewers:
- reviewers = matched_reviewers.group(1).split(',')
- parsed_lines.append(l)
-
- if len(subject) > 100:
- subject = subject[:97] + '...'
-
- self._subject = subject
- self._reviewers = reviewers
- self._description = '\n'.join(parsed_lines)
+ self._change_desc = presubmit_support.ChangeDescription(
+ description=description)
description = property(_get_description, _set_description)
@property
def reviewers(self):
- return self._reviewers
+ return self._change_desc.reviewers
@property
def subject(self):
- return self._subject
+ return self._change_desc.subject
def NeedsUpload(self):
return self.needs_upload
@@ -378,7 +355,7 @@
'patchset': self.patchset,
'needs_upload': self.NeedsUpload(),
'files': self.GetFiles(),
- 'description': self.description,
+ 'description': self._change_desc.description,
'rietveld': self.rietveld,
}, sort_keys=True, indent=2)
gclient_utils.FileWrite(GetChangelistInfoFile(self.name), data)
@@ -739,20 +716,6 @@
return 0
-def GetEditor():
- editor = os.environ.get("SVN_EDITOR")
- if not editor:
- editor = os.environ.get("EDITOR")
-
- if not editor:
- if sys.platform.startswith("win"):
- editor = "notepad"
- else:
- editor = "vi"
-
- return editor
-
-
def GenerateDiff(files, root=None):
return SVN.GenerateDiff(files, root=root)
@@ -1098,48 +1061,38 @@
affected_files = [x for x in other_files if file_re.match(x[0])]
unaffected_files = [x for x in other_files if not file_re.match(x[0])]
- if not change_info.reviewers:
+ reviewers = change_info.reviewers
+ if not reviewers:
files_for_review = affected_files[:]
files_for_review.extend(change_info.GetFiles())
- suggested_reviewers = suggest_reviewers(change_info, files_for_review)
- if suggested_reviewers:
- reviewers_re = re.compile(REVIEWERS_REGEX)
- if not any(reviewers_re.match(l) for l in description.splitlines()):
- description += '\n\nR=' + ','.join(suggested_reviewers)
-
- description = description.rstrip() + '\n'
+ reviewers = suggest_reviewers(change_info, files_for_review)
separator1 = ("\n---All lines above this line become the description.\n"
"---Repository Root: " + change_info.GetLocalRoot() + "\n"
"---Paths in this changelist (" + change_info.name + "):\n")
separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
- text = (description + separator1 + '\n' +
- '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
+
+ footer = (separator1 + '\n' +
+ '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
if change_info.Exists():
- text += (separator2 +
- '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
+ footer += (separator2 +
+ '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
else:
- text += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
- separator2)
- text += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
+ footer += ('\n'.join([f[0] + f[1] for f in affected_files]) + '\n' +
+ separator2)
+ footer += '\n'.join([f[0] + f[1] for f in unaffected_files]) + '\n'
- handle, filename = tempfile.mkstemp(text=True)
- os.write(handle, text)
- os.close(handle)
+ change_desc = presubmit_support.ChangeDescription(description=description,
+ reviewers=reviewers)
- # Open up the default editor in the system to get the CL description.
- try:
- if not silent:
- cmd = '%s %s' % (GetEditor(), filename)
- if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
- # Msysgit requires the usage of 'env' to be present.
- cmd = 'env ' + cmd
- # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
- subprocess.check_call(cmd, shell=True)
- result = gclient_utils.FileRead(filename, 'r')
- finally:
- os.remove(filename)
+ # These next few lines are equivalent to change_desc.UserUpdate(). We
+ # call them individually to avoid passing a lot of state back and forth.
+ original_description = change_desc.description
+
+ result = change_desc.EditableDescription() + footer
+ if not silent:
+ result = change_desc.editor(result)
if not result:
return 0
@@ -1151,8 +1104,8 @@
# Update the CL description if it has changed.
new_description = split_result[0]
cl_files_text = split_result[1]
- if new_description != description or override_description:
- change_info.description = new_description
+ change_desc.Parse(new_description)
+ if change_desc.description != original_description or override_description:
change_info.needs_upload = True
new_cl_files = []
@@ -1166,7 +1119,7 @@
new_cl_files.append((status, filename))
if (not len(change_info.GetFiles()) and not change_info.issue and
- not len(new_description) and not new_cl_files):
+ not len(change_desc.description) and not new_cl_files):
ErrorExit("Empty changelist not saved")
change_info._files = new_cl_files
diff --git a/gclient_utils.py b/gclient_utils.py
index 97c8227..7af1163 100644
--- a/gclient_utils.py
+++ b/gclient_utils.py
@@ -12,6 +12,7 @@
import stat
import subprocess
import sys
+import tempfile
import threading
import time
import xml.dom.minidom
@@ -710,3 +711,37 @@
work_queue.ready_cond.notifyAll()
finally:
work_queue.ready_cond.release()
+
+
+def GetEditor():
+ editor = os.environ.get("SVN_EDITOR")
+ if not editor:
+ editor = os.environ.get("EDITOR")
+
+ if not editor:
+ if sys.platform.startswith("win"):
+ editor = "notepad"
+ else:
+ editor = "vi"
+
+ return editor
+
+
+def UserEdit(text):
+ """Open an editor, edit the text, and return the result."""
+ (file_handle, filename) = tempfile.mkstemp()
+ fileobj = os.fdopen(file_handle, 'w')
+ fileobj.write(text)
+ fileobj.close()
+
+ # Open up the default editor in the system to get the CL description.
+ try:
+ cmd = '%s %s' % (GetEditor(), filename)
+ if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
+ # Msysgit requires the usage of 'env' to be present.
+ cmd = 'env ' + cmd
+ # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
+ subprocess.check_call(cmd, shell=True)
+ return FileRead(filename, 'r')
+ finally:
+ os.remove(filename)
diff --git a/git_cl/git_cl.py b/git_cl/git_cl.py
index 218c9f3..552c094 100644
--- a/git_cl/git_cl.py
+++ b/git_cl/git_cl.py
@@ -9,7 +9,6 @@
import re
import subprocess
import sys
-import tempfile
import textwrap
import urlparse
import urllib2
@@ -21,17 +20,17 @@
# TODO(dpranke): don't use relative import.
import upload # pylint: disable=W0403
-try:
- # TODO(dpranke): We wrap this in a try block for a limited form of
- # backwards-compatibility with older versions of git-cl that weren't
- # dependent on depot_tools. This version should still work outside of
- # depot_tools as long as --bypass-hooks is used. We should remove this
- # once this has baked for a while and things seem safe.
- depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- sys.path.append(depot_tools_path)
- import breakpad # pylint: disable=W0611
-except ImportError:
- pass
+
+# TODO(dpranke): move this file up a directory so we don't need this.
+depot_tools_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(depot_tools_path)
+
+import breakpad # pylint: disable=W0611
+
+import presubmit_support
+import scm
+import watchlists
+
DEFAULT_SERVER = 'http://codereview.appspot.com'
POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
@@ -333,6 +332,7 @@
self.description = None
self.has_patchset = False
self.patchset = None
+ self.tbr = False
def GetBranch(self):
"""Returns the short branch name, e.g. 'master'."""
@@ -535,53 +535,6 @@
# svn-based hackery.
-class ChangeDescription(object):
- """Contains a parsed form of the change description."""
- def __init__(self, subject, log_desc, reviewers):
- self.subject = subject
- self.log_desc = log_desc
- self.reviewers = reviewers
- self.description = self.log_desc
-
- def Update(self):
- initial_text = """# Enter a description of the change.
-# This will displayed on the codereview site.
-# The first line will also be used as the subject of the review.
-"""
- initial_text += self.description
- if 'R=' not in self.description and self.reviewers:
- initial_text += '\nR=' + self.reviewers
- if 'BUG=' not in self.description:
- initial_text += '\nBUG='
- if 'TEST=' not in self.description:
- initial_text += '\nTEST='
- self._ParseDescription(UserEditedLog(initial_text))
-
- def _ParseDescription(self, description):
- if not description:
- self.description = description
- return
-
- parsed_lines = []
- reviewers_regexp = re.compile('\s*R=(.+)')
- reviewers = ''
- subject = ''
- for l in description.splitlines():
- if not subject:
- subject = l
- matched_reviewers = reviewers_regexp.match(l)
- if matched_reviewers:
- reviewers = matched_reviewers.group(1)
- parsed_lines.append(l)
-
- self.description = '\n'.join(parsed_lines) + '\n'
- self.subject = subject
- self.reviewers = reviewers
-
- def IsEmpty(self):
- return not self.description
-
-
def FindCodereviewSettingsFile(filename='codereview.settings'):
"""Finds the given file starting in the cwd and going up.
@@ -731,36 +684,6 @@
return RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
-def UserEditedLog(starting_text):
- """Given some starting text, let the user edit it and return the result."""
- editor = os.getenv('EDITOR', 'vi')
-
- (file_handle, filename) = tempfile.mkstemp()
- fileobj = os.fdopen(file_handle, 'w')
- fileobj.write(starting_text)
- fileobj.close()
-
- # Open up the default editor in the system to get the CL description.
- try:
- cmd = '%s %s' % (editor, filename)
- if sys.platform == 'win32' and os.environ.get('TERM') == 'msys':
- # Msysgit requires the usage of 'env' to be present.
- cmd = 'env ' + cmd
- # shell=True to allow the shell to handle all forms of quotes in $EDITOR.
- subprocess.check_call(cmd, shell=True)
- fileobj = open(filename)
- text = fileobj.read()
- fileobj.close()
- finally:
- os.remove(filename)
-
- if not text:
- return
-
- stripcomment_re = re.compile(r'^#.*$', re.MULTILINE)
- return stripcomment_re.sub('', text).strip()
-
-
def ConvertToInteger(inputval):
"""Convert a string to integer, but returns either an int or None."""
try:
@@ -769,12 +692,20 @@
return None
+class GitChangeDescription(presubmit_support.ChangeDescription):
+ def UserEdit(self):
+ header = (
+ "# Enter a description of the change.\n"
+ "# This will displayed on the codereview site.\n"
+ "# The first line will also be used as the subject of the review.\n"
+ "\n")
+ edited_text = self.editor(header + self.EditableDescription())
+ stripcomment_re = re.compile(r'^#.*$', re.MULTILINE)
+ self.Parse(stripcomment_re.sub('', edited_text).strip())
+
+
def RunHook(committing, upstream_branch, rietveld_server, tbr, may_prompt):
"""Calls sys.exit() if the hook fails; returns a HookResults otherwise."""
- import presubmit_support
- import scm
- import watchlists
-
root = RunCommand(['git', 'rev-parse', '--show-cdup']).strip()
if not root:
root = '.'
@@ -843,13 +774,13 @@
if options.upload:
print '*** Presubmit checks for UPLOAD would report: ***'
RunHook(committing=False, upstream_branch=base_branch,
- rietveld_server=cl.GetRietveldServer(), tbr=False,
+ rietveld_server=cl.GetRietveldServer(), tbr=cl.tbr,
may_prompt=False)
return 0
else:
print '*** Presubmit checks for DCOMMIT would report: ***'
RunHook(committing=True, upstream_branch=base_branch,
- rietveld_server=cl.GetRietveldServer, tbr=False,
+ rietveld_server=cl.GetRietveldServer, tbr=cl.tbr,
may_prompt=False)
return 0
@@ -893,10 +824,10 @@
if not options.bypass_hooks and not options.force:
hook_results = RunHook(committing=False, upstream_branch=base_branch,
- rietveld_server=cl.GetRietveldServer(), tbr=False,
+ rietveld_server=cl.GetRietveldServer(), tbr=cl.tbr,
may_prompt=True)
if not options.reviewers and hook_results.reviewers:
- options.reviewers = hook_results.reviewers
+ options.reviewers = ','.join(hook_results.reviewers)
# --no-ext-diff is broken in some versions of Git, so try to work around
@@ -930,10 +861,10 @@
"Adding patch to that issue." % cl.GetIssue())
else:
log_desc = CreateDescriptionFromLog(args)
- change_desc = ChangeDescription(options.message, log_desc,
- options.reviewers)
- if not options.from_logs:
- change_desc.Update()
+ change_desc = GitChangeDescription(subject=options.message,
+ description=log_desc, reviewers=options.reviewers, tbr=cl.tbr)
+ if not options.from_logs and (not options.force):
+ change_desc.UserEdit()
if change_desc.IsEmpty():
print "Description is empty; aborting."
@@ -1044,7 +975,7 @@
if not options.bypass_hooks and not options.force:
RunHook(committing=True, upstream_branch=base_branch,
- rietveld_server=cl.GetRietveldServer(), tbr=options.tbr,
+ rietveld_server=cl.GetRietveldServer(), tbr=(cl.tbr or options.tbr),
may_prompt=True)
if cmd == 'dcommit':
@@ -1083,17 +1014,15 @@
# create a template description. Eitherway, give the user a chance to edit
# it to fill in the TBR= field.
if cl.GetIssue():
- description = cl.GetDescription()
+ change_desc = GitChangeDescription(description=cl.GetDescription())
- # TODO(dpranke): Update to use ChangeDescription object.
if not description:
- description = """# Enter a description of the change.
-# This will be used as the change log for the commit.
+ log_desc = CreateDescriptionFromLog(args)
+ change_desc = GitChangeDescription(description=log_desc, tbr=True)
-"""
- description += CreateDescriptionFromLog(args)
-
- description = UserEditedLog(description + '\nTBR=')
+ if not options.force:
+ change_desc.UserEdit()
+ description = change_desc.description
if not description:
print "Description empty; aborting."
diff --git a/presubmit_support.py b/presubmit_support.py
index f1f70d9..3b98475 100755
--- a/presubmit_support.py
+++ b/presubmit_support.py
@@ -812,6 +812,96 @@
self.scm = 'git'
+class ChangeDescription(object):
+ """Contains a parsed form of the change description."""
+ MAX_SUBJECT_LENGTH = 100
+
+ def __init__(self, subject=None, description=None, reviewers=None, tbr=False,
+ editor=None):
+ self.subject = (subject or '').strip()
+ self.description = (description or '').strip()
+ self.reviewers = reviewers or []
+ self.tbr = tbr
+ self.editor = editor or gclient_utils.UserEdit
+
+ if self.description:
+ if not self.description.startswith(self.subject):
+ self.description = self.subject + '\n\n' + self.description
+ elif self.subject:
+ self.description = self.subject
+ self.Parse(self.EditableDescription())
+
+ def EditableDescription(self):
+ text = self.description.strip()
+ if text:
+ text += '\n'
+
+ tbr_present = False
+ r_present = False
+ bug_present = False
+ test_present = False
+ for l in text.splitlines():
+ l = l.strip()
+ r_present = r_present or l.startswith('R=')
+ tbr_present = tbr_present or l.startswith('TBR=')
+
+ if text and not (r_present or tbr_present):
+ text += '\n'
+
+ if not tbr_present and not r_present:
+ if self.tbr:
+ text += 'TBR=' + ','.join(self.reviewers) + '\n'
+ else:
+ text += 'R=' + ','.join(self.reviewers) + '\n'
+ if not bug_present:
+ text += 'BUG=\n'
+ if not test_present:
+ text += 'TEST=\n'
+
+ return text
+
+ def UserEdit(self):
+ """Allows the user to update the description.
+
+ Uses the editor callback passed to the constructor."""
+ self.Parse(self.editor(self.EditableDescription()))
+
+ def Parse(self, text):
+ """Parse the text returned from UserEdit() and update our state."""
+ parsed_lines = []
+ reviewers_regexp = re.compile('\s*(TBR|R)=(.+)')
+ reviewers = []
+ subject = ''
+ tbr = False
+ for l in text.splitlines():
+ l = l.strip()
+
+ # Throw away empty BUG=, TEST=, and R= lines. We leave in TBR= lines
+ # to indicate that this change was meant to be "unreviewed".
+ if l in ('BUG=', 'TEST=', 'R='):
+ continue
+
+ if not subject:
+ subject = l
+ matched_reviewers = reviewers_regexp.match(l)
+ if matched_reviewers:
+ tbr = (matched_reviewers.group(1) == 'TBR')
+ reviewers.extend(matched_reviewers.group(2).split(','))
+ parsed_lines.append(l)
+
+ if len(subject) > self.MAX_SUBJECT_LENGTH:
+ subject = subject[:self.MAX_SUBJECT_LENGTH - 3] + '...'
+
+ self.description = '\n'.join(parsed_lines).strip()
+ self.subject = subject
+ self.reviewers = reviewers
+ self.tbr = tbr
+
+ def IsEmpty(self):
+ return not self.description
+
+
+
def ListRelevantPresubmitFiles(files, root):
"""Finds all presubmit files that apply to a given set of source files.
diff --git a/tests/gcl_unittest.py b/tests/gcl_unittest.py
index 4ab2b6f..4e41d13 100755
--- a/tests/gcl_unittest.py
+++ b/tests/gcl_unittest.py
@@ -84,7 +84,7 @@
'ErrorExit', 'FILES_CACHE', 'FilterFlag', 'GenUsage',
'GenerateChangeName', 'GenerateDiff', 'GetCLs', 'GetCacheDir',
'GetCachedFile', 'GetChangelistInfoFile', 'GetChangesDir',
- 'GetCodeReviewSetting', 'GetEditor', 'GetFilesNotInCL', 'GetInfoDir',
+ 'GetCodeReviewSetting', 'GetFilesNotInCL', 'GetInfoDir',
'GetModifiedFiles', 'GetRepositoryRoot', 'ListFiles',
'LoadChangelistInfoForMultiple', 'MISSING_TEST_MSG',
'OptionallyDoPresubmitChecks', 'REPOSITORY_ROOT', 'REVIEWERS_REGEX',
diff --git a/tests/gclient_utils_test.py b/tests/gclient_utils_test.py
index 8027675..ccbff65 100755
--- a/tests/gclient_utils_test.py
+++ b/tests/gclient_utils_test.py
@@ -28,13 +28,13 @@
'CheckCall', 'CheckCallError', 'CheckCallAndFilter',
'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead',
'FileWrite', 'FindFileUpwards', 'FindGclientRoot',
- 'GetGClientRootAndEntries', 'GetNamedNodeText', 'MakeFileAutoFlush',
- 'GetNodeNamedAttributeText', 'MakeFileAnnotated', 'PathDifference',
- 'ParseXML', 'Popen',
+ 'GetGClientRootAndEntries', 'GetEditor', 'GetNamedNodeText',
+ 'MakeFileAutoFlush', 'GetNodeNamedAttributeText', 'MakeFileAnnotated',
+ 'PathDifference', 'ParseXML', 'Popen',
'PrintableObject', 'RemoveDirectory', 'SoftClone', 'SplitUrlRevision',
- 'SyntaxErrorToError', 'WorkItem',
+ 'SyntaxErrorToError', 'UserEdit', 'WorkItem',
'errno', 'hack_subprocess', 'logging', 'os', 'Queue', 're', 'rmtree',
- 'stat', 'subprocess', 'sys','threading', 'time', 'xml',
+ 'stat', 'subprocess', 'sys', 'tempfile', 'threading', 'time', 'xml',
]
# If this test fails, you should add the relevant test.
self.compareMembers(gclient_utils, members)
diff --git a/tests/presubmit_unittest.py b/tests/presubmit_unittest.py
index b2cb190..28eafda 100755
--- a/tests/presubmit_unittest.py
+++ b/tests/presubmit_unittest.py
@@ -9,6 +9,7 @@
# pylint: disable=E1101,E1103,W0212,W0403
import StringIO
+import unittest
# Fixes include path.
from super_mox import mox, SuperMoxTestBase
@@ -135,8 +136,8 @@
def testMembersChanged(self):
self.mox.ReplayAll()
members = [
- 'AffectedFile', 'Change', 'DoGetTrySlaves', 'DoPresubmitChecks',
- 'GetTrySlavesExecuter', 'GitAffectedFile',
+ 'AffectedFile', 'Change', 'ChangeDescription', 'DoGetTrySlaves',
+ 'DoPresubmitChecks', 'GetTrySlavesExecuter', 'GitAffectedFile',
'GitChange', 'InputApi', 'ListRelevantPresubmitFiles', 'Main',
'NotImplementedException', 'OutputApi', 'ParseFiles',
'PresubmitExecuter', 'PresubmitOutput', 'ScanSubDirs',
@@ -1971,6 +1972,81 @@
uncovered_files=set(), host_url='https://localhost')
+def change_desc(editor=None, **kwargs):
+ if editor is None:
+ editor = lambda x: x
+ return presubmit.ChangeDescription(editor=editor, **kwargs)
+
+
+class ChangeDescriptionTests(unittest.TestCase):
+ def setUp(self):
+ self.editor_input = None
+ self.editor_output = None
+
+ def tearDown(self):
+ self.editor_input = None
+ self.editor_output = None
+
+ def editor(self, text):
+ if self.editor_input:
+ self.assertTrue(self.editor_input in text)
+ if self.editor_output is not None:
+ return self.editor_output
+ return text
+
+ def test_empty(self):
+ desc = change_desc()
+ self.assertTrue(desc.IsEmpty())
+ desc.UserEdit()
+ self.assertTrue(desc.IsEmpty())
+
+ def test_basic(self):
+ desc = change_desc(subject='foo', description='desc',
+ reviewers=['[email protected]'])
+ desc.UserEdit()
+ self.assertFalse(desc.IsEmpty())
+ self.assertEqual(desc.subject, 'foo')
+ self.assertEquals(desc.description,
+ 'foo\n'
+ '\n'
+ 'desc\n'
+ '\n'
+ '[email protected]')
+ self.assertEquals(desc.reviewers, ['[email protected]'])
+ self.assertFalse(desc.tbr)
+
+ def test_subject_only(self):
+ self.editor_input = 'foo\n\nR=\nBUG=\nTEST=\n'
+ desc = change_desc(subject='foo', editor=self.editor)
+ desc.UserEdit()
+ self.assertEquals(desc.description, 'foo')
+
+ def test_tbr_with_reviewer(self):
+ self.editor_input = 'TBR=\nBUG=\nTEST=\n'
+ self.editor_output = 'foo\n\[email protected]'
+ desc = change_desc(tbr=True, editor=self.editor)
+ self.assertFalse(desc.tbr)
+ self.assertEquals(desc.reviewers, [])
+ desc.UserEdit()
+ self.assertTrue(desc.tbr)
+ self.assertEquals(desc.reviewers, ['[email protected]'])
+ self.assertEquals(desc.description,
+ 'foo\n'
+ '\n'
+ '[email protected]')
+
+ def test_tbr_without_reviewer(self):
+ desc = change_desc(subject='foo', tbr=True)
+ desc.UserEdit()
+ self.assertEquals(desc.description, 'foo\n\nTBR=')
+
+ def test_really_long_subject(self):
+ subject = 'foo' * 40
+ desc = change_desc(subject=subject)
+ self.assertEquals(desc.description, subject)
+ self.assertEquals(desc.subject, subject[:97] + '...')
+
+
if __name__ == '__main__':
import unittest
unittest.main()