Merge lp:~ursinha/launchpad/auto-approve into lp:launchpad

Proposed by Ursula Junque
Status: Work in progress
Proposed branch: lp:~ursinha/launchpad/auto-approve
Merge into: lp:launchpad
Diff against target: 526 lines (+260/-15)
6 files modified
lib/devscripts/autoland.py (+59/-0)
lib/devscripts/ec2test/builtins.py (+21/-5)
lib/devscripts/ec2test/remote.py (+57/-4)
lib/devscripts/ec2test/testrunner.py (+82/-3)
lib/devscripts/ec2test/tests/test_remote.py (+3/-2)
lib/devscripts/tests/test_autoland.py (+38/-1)
To merge this branch: bzr merge lp:~ursinha/launchpad/auto-approve
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+45850@code.launchpad.net

Description of the change

These changes implement ec2 land capability to work with the new Launchpad Tarmac merge workflow.

= Summary =

This branch adds the -T (--use-tarmac) option to ec2 land, so when the tests finish, one "tests" vote is added to the merge proposal, and its queue status is set to approved or rejected.
For this to work, I had to add a --mergeproposal-address to ec2 test, considering ec2 land is just a wrapper that generates an ec2 test command line and runs it as a subprocess.

== Tests ==

I've tested the newly created method by adding more tests to test_autoland.py.

./bin/test -vvt devscripts.*

== Demo and Q/A ==

I've tested by running the command in a real merge proposal in Launchpad, using the --dry-run option, to check if the command line is correctly generated by ec2 land.

$ ec2 land <merge_proposal> --force --dry-run --use-tarmac

Should show the command line with the ec2 test --mergeproposal-address I've added so it can generate the signed message to send launchpad after the tests.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (18.6 KiB)

So now we're getting "tests" votes? Nice! That's something I've been wanting.

The branch looks good overall, though it wouldn't be me if there weren't some comments.

Some of those are things that need changing. Most of them are mere suggestions. I trust you to get the changes right, so I'm giving you an approval vote. But feel free to ask if you do want them reviewed, of course.

> === modified file 'lib/devscripts/autoland.py'
> --- lib/devscripts/autoland.py 2011-01-04 20:22:39 +0000
> +++ lib/devscripts/autoland.py 2011-01-11 13:08:59 +0000
> @@ -6,6 +6,8 @@
> STAGING_SERVICE_ROOT)
> from lazr.uri import URI
> from bzrlib.errors import BzrCommandError
> +from bzrlib.email_message import EmailMessage
> +from bzrlib.config import GlobalConfig

Those should be in alphabetical order. Which is a drag to keep up. Run utilities/format-new-and-modified-imports to automate the drudgery.

> @@ -191,6 +193,63 @@
> self._mp.lp_save()
>
>
> +class MergeProposalMessage(EmailMessage):
> +
> + def __init__(self, mail_to, mail_from, successful=None,
> + source_branch=None, gpg_strategy=None):

Our coding standards say the continuation line(s) for the parameters in the method definition (not in calls mind you!) should be indented to the same column as the first parameter.

So the "source_branch" parameter aligns with "self." If this comes through correctly:

    def __init__(self, mail_to, mail_from, successful=None,
                 source_branch=None, gpg_strategy=None):

> + if successful:
> + self.subject = "Test results: SUCCESS"
> + self.body_text = """
> +Full test results were mailed to the submitter.
> +
> + review approve tests
> + merge approved
> +"""
> + else:
> + self.subject = "Test results: FAILURE"
> + self.body_text = """
> +Full test results were mailed to the submitter.
> +
> + review needsfixing tests
> + merge rejected
> +"""

Getting rid of unwanted indentation in multi-line strings is always a pain. Dedent() alone is not enough because the string starts out with a newline: thus not every line begins with indentation. Personally I consider it a bug but we have to live with it.

One question though: those two body texts are identical in structure and similar in text. How significant is that? If it's something inherent, it may help to abstract away the differences. For example:

    if successful:
        test_outcome = "SUCCESS"
        vote = "approve"
        status = "approved"
    else:
        test_outcome = "FAILURE"
        vote = "needsfixing"
        status = "rejected"

    self.subject = "Test results: %s" % test_outcome
    self.body_text = '\n'.join([
        "Full test results were mailed to the submitter.",
        "",
        "review %s tests" % vote,
        "merge %s" % status,
        ])

Mind you, just an example. Another approach might be to create an email template in a separate file, as in lib/lp/*/emailtemplates. But it hardly seems worth the trouble.

> + self.successful = successful
> + self.source_branch = source_branch
> + self.mail_from = mail_from
> + self.mail_to = mail_...

review: Approve (code)
Revision history for this message
Robert Collins (lifeless) wrote :

Its my understanding that tarmac is on hold - we're unlikely to even get close to it in 2011; to avoid having an unused codepath I'm going to suggest we don't land this at this point - I'm putting this proposal into 'WIP' status until we get back to working on our landing story.

Unmerged revisions

12054. By Ursula Junque

Added tests to MergeProposalMessage class

12053. By Ursula Junque

moved MergeProposalMessage to autoland.

12052. By Ursula Junque

moved MergeProposalMessage to autoland.py, where MergeProposal is defined.

12051. By Ursula Junque

Merging latest devel

12050. By Ursula Junque

fixed spacing

12049. By Ursula Junque

change parameters when calling Request

12048. By Ursula Junque

Added a MergeProposalMessage class to represent a signed message to be sent to the MP, changing it according to the test results.

12047. By Ursula Junque

removing the signing phase of merge proposal messages, now it gets both preassembled success/failure messages and decodes as it does with pqm_message

12046. By Ursula Junque

updated tests and standarized variable and methods names

12045. By Ursula Junque

adding workaround to ask the user signature before tests start, to after that email the results to the launchpad mp. It also adds that if mergeproposal address is provided, pqm submission is ignored.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/autoland.py'
2--- lib/devscripts/autoland.py 2011-01-04 20:22:39 +0000
3+++ lib/devscripts/autoland.py 2011-01-11 13:08:59 +0000
4@@ -6,6 +6,8 @@
5 STAGING_SERVICE_ROOT)
6 from lazr.uri import URI
7 from bzrlib.errors import BzrCommandError
8+from bzrlib.email_message import EmailMessage
9+from bzrlib.config import GlobalConfig
10
11
12 class MissingReviewError(Exception):
13@@ -191,6 +193,63 @@
14 self._mp.lp_save()
15
16
17+class MergeProposalMessage(EmailMessage):
18+
19+ def __init__(self, mail_to, mail_from, successful=None,
20+ source_branch=None, gpg_strategy=None):
21+
22+ if successful:
23+ self.subject = "Test results: SUCCESS"
24+ self.body_text = """
25+Full test results were mailed to the submitter.
26+
27+ review approve tests
28+ merge approved
29+"""
30+ else:
31+ self.subject = "Test results: FAILURE"
32+ self.body_text = """
33+Full test results were mailed to the submitter.
34+
35+ review needsfixing tests
36+ merge rejected
37+"""
38+ self.successful = successful
39+ self.source_branch = source_branch
40+ self.mail_from = mail_from
41+ self.mail_to = mail_to
42+ self.gpg_strategy = gpg_strategy
43+
44+ def to_signed(self, unsigned_text):
45+ """Serialize as a signed string."""
46+
47+ if self.source_branch:
48+ config = self.source_branch.get_config()
49+ else:
50+ config = GlobalConfig()
51+ if self.gpg_strategy is None:
52+ strategy = GPGStrategy(config)
53+ else:
54+ strategy = self.gpg_strategy
55+ return strategy.sign(unsigned_text)
56+
57+ def to_email(self, mail_from=None, mail_to=None):
58+ """Serialize as an email message.
59+
60+ :param mail_from: The from address for the message
61+ :param mail_to: The address to send the message to
62+ :return: an email message
63+ """
64+ if mail_from is None:
65+ mail_from = self.mail_from
66+ if mail_to is None:
67+ mail_to = self.mail_to
68+ body = self.to_signed(self.body_text)
69+ message = EmailMessage(mail_from, mail_to, self.subject, body)
70+ return message
71+
72+
73+
74 def get_testfix_clause(testfix=False):
75 """Get the testfix clause."""
76 if testfix:
77
78=== modified file 'lib/devscripts/ec2test/builtins.py'
79--- lib/devscripts/ec2test/builtins.py 2010-11-29 21:32:06 +0000
80+++ lib/devscripts/ec2test/builtins.py 2011-01-11 13:08:59 +0000
81@@ -223,6 +223,12 @@
82 "to ``-o '-vv'``. For instance, to run specific tests, "
83 "you might use ``-o '-vvt my_test_pattern'``.")),
84 Option(
85+ 'mergeproposal-address', type=str,
86+ help=('Address of the merge proposal to be updated with test'
87+ 'results. Should be used only by ec2 land. If provided, you '
88+ 'will be asked for your GPG passphrase before the test run '
89+ 'begins.')),
90+ Option(
91 'submit-pqm-message', short_name='s', type=str, argname="MSG",
92 help=(
93 'A pqm message to submit if the test run is successful. If '
94@@ -271,7 +277,7 @@
95 noemail=False, submit_pqm_message=None, pqm_public_location=None,
96 pqm_submit_location=None, pqm_email=None, postmortem=False,
97 attached=False, debug=False, open_browser=False,
98- include_download_cache_changes=False):
99+ include_download_cache_changes=False, mergeproposal_address=None):
100 if debug:
101 pdb.set_trace()
102 if branch is None:
103@@ -312,7 +318,7 @@
104 open_browser=open_browser, pqm_email=pqm_email,
105 include_download_cache_changes=include_download_cache_changes,
106 instance=instance, launchpad_login=instance._launchpad_login,
107- timeout=480)
108+ timeout=480, mergeproposal_address=mergeproposal_address)
109
110 instance.set_up_and_run(postmortem, attached, runner.run_tests)
111
112@@ -324,6 +330,8 @@
113 debug_option,
114 Option('dry-run', help="Just print the equivalent ec2 test command."),
115 Option('print-commit', help="Print the full commit message."),
116+ Option('use-tarmac', short_name='T', help="This should be prepared "
117+ "for Tarmac landing instead of PQM."),
118 Option(
119 'testfix',
120 help="This is a testfix (tags commit with [testfix])."),
121@@ -352,7 +360,7 @@
122 takes_args = ['merge_proposal?']
123
124 def _get_landing_command(self, source_url, target_url, commit_message,
125- emails, attached):
126+ emails, attached, mergeproposal_address=None):
127 """Return the command that would need to be run to submit with ec2."""
128 ec2_path = os.path.join(get_launchpad_root(), 'utilities', 'ec2')
129 command = [ec2_path, 'test']
130@@ -366,13 +374,16 @@
131 command.extend(
132 ['-b', 'launchpad=%s' % (target_branch_name), '-s',
133 commit_message, str(source_url)])
134+ if mergeproposal_address:
135+ command.extend(
136+ ['--mergeproposal-address=%s' % mergeproposal_address])
137 return command
138
139 def run(self, merge_proposal=None, machine=None,
140 instance_type=DEFAULT_INSTANCE_TYPE, postmortem=False,
141 debug=False, commit_text=None, dry_run=False, testfix=False,
142 no_qa=False, incremental=False, rollback=None, print_commit=False,
143- force=False, attached=False):
144+ force=False, attached=False, use_tarmac=False):
145 try:
146 from devscripts.autoland import (
147 LaunchpadBranchLander, MissingReviewError, MissingBugsError,
148@@ -447,9 +458,14 @@
149 print commit_message
150 return
151
152+ if use_tarmac:
153+ mergeproposal_address= mp._mp.address
154+ else:
155+ mergeproposal_address = None
156+
157 landing_command = self._get_landing_command(
158 mp.source_branch, mp.target_branch, commit_message,
159- mp.get_stakeholder_emails(), attached)
160+ mp.get_stakeholder_emails(), attached, mergeproposal_address)
161 if dry_run:
162 print landing_command
163 else:
164
165=== modified file 'lib/devscripts/ec2test/remote.py'
166--- lib/devscripts/ec2test/remote.py 2010-10-26 15:47:24 +0000
167+++ lib/devscripts/ec2test/remote.py 2011-01-11 13:08:59 +0000
168@@ -306,7 +306,8 @@
169 """A request to have a branch tested and maybe landed."""
170
171 def __init__(self, branch_url, revno, local_branch_path, sourcecode_path,
172- emails=None, pqm_message=None, smtp_connection=None):
173+ emails=None, success_message=None, failure_message=None,
174+ pqm_message=None, smtp_connection=None):
175 """Construct a `Request`.
176
177 :param branch_url: The public URL to the Launchpad branch we are
178@@ -320,6 +321,12 @@
179 dependencies.
180 :param emails: A list of emails to send the results to. If not
181 provided, no emails are sent.
182+ :param success_message: The message to update merge proposal in
183+ Launchpad with successful results. If not provided, we don't
184+ change the merge proposal.
185+ :param failure_message: The message to update merge proposal in
186+ Launchpad with failure results. If not provided, we don't
187+ change the merge proposal.
188 :param pqm_message: The message to submit to PQM. If not provided, we
189 don't submit to PQM.
190 :param smtp_connection: The `SMTPConnection` to use to send email.
191@@ -330,6 +337,8 @@
192 self._sourcecode_path = sourcecode_path
193 self._emails = emails
194 self._pqm_message = pqm_message
195+ self._success_message = success_message
196+ self._failure_message = failure_message
197 # Used for figuring out how to send emails.
198 self._bzr_config = bzrlib.config.GlobalConfig()
199 if smtp_connection is None:
200@@ -502,6 +511,18 @@
201 message = self._build_report_email(successful, body_text, full_log_gz)
202 self._send_email(message)
203
204+ def send_mergeproposal_email(self, successful):
205+ """Send an email to the merge proposal adding a tests vote, and
206+ approving or disapproving it, according to test results.
207+
208+ :param successful: True for pass, False for failure.
209+ """
210+ if successful:
211+ message = self._success_message
212+ else:
213+ message = self._failure_message
214+ self._send_email(message)
215+
216 def iter_dependency_branches(self):
217 """Iterate through the Bazaar branches we depend on."""
218 for name in sorted(os.listdir(self._sourcecode_path)):
219@@ -596,6 +617,8 @@
220 summary = self.get_summary_contents()
221 full_log_gz = gzip_data(self.get_full_log_contents())
222 self._request.send_report_email(False, summary, full_log_gz)
223+ if (self._request._failure_message is not None):
224+ self._request.send_mergeproposal_email(False)
225
226 def get_index_contents(self):
227 """Return the contents of the index.html page."""
228@@ -632,7 +655,11 @@
229 """The tests are done and the results are known."""
230 self._end_time = datetime.datetime.utcnow()
231 successful = result.wasSuccessful()
232- self._handle_pqm_submission(successful)
233+ if (self._request._success_message is None and
234+ self._request._failure_message is None):
235+ self._handle_pqm_submission(successful)
236+ else:
237+ self._request.send_mergeproposal_email(successful)
238 if self._request.wants_email:
239 email_text = self._request.format_result(
240 result, self._start_time, self._end_time)
241@@ -846,6 +873,22 @@
242 'the email address from `bzr whoami`. May be supplied multiple '
243 'times. `bzr whoami` will be used as the From: address.'))
244 parser.add_option(
245+ '--success-message', dest='success_message', default=None,
246+ help=('A base64-encoded pickle (string) of a email message to be '
247+ 'sent to Launchpad, updating the merge proposal by adding a '
248+ 'tests vote with an approval, and setting the merge proposal '
249+ 'status to approved. Should be used only by ec2 land. If '
250+ 'provided, you will be asked for your GPG passphrase before '
251+ 'the test run begins.'))
252+ parser.add_option(
253+ '--failure-message', dest='failure_message', default=None,
254+ help=('A base64-encoded pickle (string) of a email message to be '
255+ 'sent to Launchpad, updating the merge proposal by adding a '
256+ 'tests vote with a needsfixing, and setting the merge proposal '
257+ 'status to rejected. Should be used only by ec2 land. If '
258+ 'provided, you will be asked for your GPG passphrase before '
259+ 'the test run begins.'))
260+ parser.add_option(
261 '-s', '--submit-pqm-message', dest='pqm_message', default=None,
262 help=('A base64-encoded pickle (string) of a pqm message '
263 '(bzrib.plugins.pqm.pqm_submit.PQMEmailMessage) to submit if '
264@@ -877,11 +920,20 @@
265
266 if options.debug:
267 import pdb; pdb.set_trace()
268- if options.pqm_message is not None:
269+ if (options.success_message is not None and
270+ options.failure_message is not None):
271+ pqm_message = None
272+ success_message = pickle.loads(
273+ options.success_message.decode('string-escape').decode('base64'))
274+ failure_message = pickle.loads(
275+ options.failure_message.decode('string-escape').decode('base64'))
276+ elif options.pqm_message is not None:
277 pqm_message = pickle.loads(
278 options.pqm_message.decode('string-escape').decode('base64'))
279+ success_message = failure_message = None
280 else:
281 pqm_message = None
282+ success_message = failure_message = None
283
284 # Locations for Launchpad. These are actually defined by the configuration
285 # of the EC2 image that we use.
286@@ -895,7 +947,8 @@
287
288 request = Request(
289 options.public_branch, options.public_branch_revno, TEST_DIR,
290- SOURCECODE_DIR, options.email, pqm_message, smtp_connection)
291+ SOURCECODE_DIR, options.email, success_message, failure_message,
292+ pqm_message, smtp_connection)
293 # Only write to stdout if we are running as the foreground process.
294 echo_to_stdout = not options.daemon
295 logger = WebTestLogger.make_in_directory(
296
297=== modified file 'lib/devscripts/ec2test/testrunner.py'
298--- lib/devscripts/ec2test/testrunner.py 2010-08-18 17:40:29 +0000
299+++ lib/devscripts/ec2test/testrunner.py 2011-01-11 13:08:59 +0000
300@@ -18,6 +18,9 @@
301 from bzrlib.bzrdir import BzrDir
302 from bzrlib.config import GlobalConfig
303 from bzrlib.errors import UncommittedChanges
304+from bzrlib.gpg import GPGStrategy
305+from bzrlib.email_message import EmailMessage
306+from devscripts.autoland import MergeProposalMessage
307 from bzrlib.plugins.pqm.pqm_submit import (
308 NoPQMSubmissionAddress, PQMSubmission)
309
310@@ -102,6 +105,57 @@
311 return dict(map(normalize_branch_input, branches))
312
313
314+class MergeProposalMessage(EmailMessage):
315+
316+ def __init__(self, mail_to, mail_from, successful=None,
317+ source_branch=None):
318+
319+ if successful:
320+ self.subject = "Test results: SUCCESS"
321+ self.body_text = """
322+Full test results were mailed to the submitter.
323+
324+ review approve tests
325+ merge approved
326+"""
327+ else:
328+ self.subject = "Test results: FAILURE"
329+ self.body_text = """
330+Full test results were mailed to the submitter.
331+
332+ review needsfixing tests
333+ merge rejected
334+"""
335+ self.source_branch = source_branch
336+ self.mail_from = mail_from
337+ self.mail_to = mail_to
338+
339+ def to_signed(self, unsigned_text):
340+ """Serialize as a signed string."""
341+
342+ if self.source_branch:
343+ config = self.source_branch.get_config()
344+ else:
345+ config = GlobalConfig()
346+ strategy = GPGStrategy(config)
347+ return strategy.sign(unsigned_text)
348+
349+ def to_email(self, mail_from=None, mail_to=None):
350+ """Serialize as an email message.
351+
352+ :param mail_from: The from address for the message
353+ :param mail_to: The address to send the message to
354+ :return: an email message
355+ """
356+ if mail_from is None:
357+ mail_from = self.mail_from
358+ if mail_to is None:
359+ mail_to = self.mail_to
360+ body = self.to_signed(self.body_text)
361+ message = EmailMessage(mail_from, mail_to, self.subject, body)
362+ return message
363+
364+
365 class EC2TestRunner:
366
367 name = 'ec2-test-runner'
368@@ -116,7 +170,7 @@
369 open_browser=False, pqm_email=None,
370 include_download_cache_changes=None, instance=None,
371 launchpad_login=None,
372- timeout=None):
373+ timeout=None, mergeproposal_address=None):
374 """Create a new EC2TestRunner.
375
376 :param timeout: Number of minutes before we force a shutdown. This is
377@@ -143,6 +197,7 @@
378 self.file = file
379 self._launchpad_login = launchpad_login
380 self.timeout = timeout
381+ self.mergeproposal_address = mergeproposal_address
382
383 trunk_specified = False
384 trunk_branch = TRUNK_BRANCH
385@@ -199,6 +254,13 @@
386 pqm_submit_location = trunk_branch
387 elif pqm_submit_location is None and trunk_specified:
388 pqm_submit_location = trunk_branch
389+
390+ # Explicitly ignoring pqm submission if the merge proposal
391+ # address is provided. That means that we should use tarmac
392+ # instead of pqm.
393+ if self.mergeproposal_address is not None:
394+ pqm_message = None
395+
396 # modified from pqm_submit.py
397 submission = PQMSubmission(
398 source_branch=bzrbranch,
399@@ -236,7 +298,20 @@
400 cache_basis_tree.unlock()
401 finally:
402 cache_tree.unlock()
403- if pqm_message is not None:
404+
405+ if mergeproposal_address is not None:
406+ self.message = None
407+ self.success_message = MergeProposalMessage(
408+ mergeproposal_address,
409+ config.username(),
410+ successful=True,
411+ source_branch=bzrbranch).to_email()
412+ self.failure_message = MergeProposalMessage(
413+ mergeproposal_address,
414+ config.username(),
415+ successful=False,
416+ source_branch=bzrbranch).to_email()
417+ elif pqm_message is not None:
418 if self.download_cache_additions:
419 raise UncommittedChanges(cache_tree)
420 # get the submission message
421@@ -255,6 +330,7 @@
422 raise NoPQMSubmissionAddress(bzrbranch)
423 mail_to = pqm_email.encode('utf8') # same here
424 self.message = submission.to_email(mail_from, mail_to)
425+ self.success_message = self.failure_message = None
426 elif (self.download_cache_additions and
427 self.include_download_cache_changes is None):
428 raise UncommittedChanges(
429@@ -264,6 +340,8 @@
430 '--ignore-download-cache-changes, -c and -g '
431 'respectively), or '
432 'commit or remove the files in the download-cache.')
433+
434+
435 self._branch = branch
436
437 if email is not False:
438@@ -287,7 +365,8 @@
439 self.email = email
440
441 # Email configuration.
442- if email is not None or pqm_message is not None:
443+ if (email is not None or pqm_message is not None or
444+ mergeproposal_address is not None):
445 self._smtp_server = config.get_user_option('smtp_server')
446 # Refuse localhost, because there's no SMTP server _on the actual
447 # EC2 instance._
448
449=== modified file 'lib/devscripts/ec2test/tests/test_remote.py'
450--- lib/devscripts/ec2test/tests/test_remote.py 2010-10-06 11:46:51 +0000
451+++ lib/devscripts/ec2test/tests/test_remote.py 2011-01-11 13:08:59 +0000
452@@ -74,7 +74,8 @@
453
454 def make_request(self, branch_url=None, revno=None,
455 trunk=None, sourcecode_path=None,
456- emails=None, pqm_message=None, emails_sent=None):
457+ emails=None, success_message=None, failure_message=None,
458+ pqm_message=None, emails_sent=None):
459 """Make a request to test, specifying only things we care about.
460
461 Note that the returned request object will not ever send email, but
462@@ -93,7 +94,7 @@
463 smtp_connection = LoggingSMTPConnection(emails_sent)
464 request = Request(
465 branch_url, revno, trunk.basedir, sourcecode_path, emails,
466- pqm_message, smtp_connection)
467+ success_message, failure_message, pqm_message, smtp_connection)
468 return request
469
470 def make_sourcecode(self, branches):
471
472=== modified file 'lib/devscripts/tests/test_autoland.py'
473--- lib/devscripts/tests/test_autoland.py 2010-11-05 18:48:39 +0000
474+++ lib/devscripts/tests/test_autoland.py 2011-01-11 13:08:59 +0000
475@@ -16,7 +16,7 @@
476 get_bazaar_host, get_bugs_clause, get_reviewer_clause,
477 get_reviewer_handle, get_qa_clause, get_testfix_clause,
478 MissingReviewError, MissingBugsError, MissingBugsIncrementalError,
479- MergeProposal)
480+ MergeProposal, MergeProposalMessage)
481
482
483 class FakeBug:
484@@ -65,6 +65,43 @@
485 pass
486
487
488+class FakeGPGStrategy:
489+
490+ def __init__(self, text_to_return=""):
491+ self.sign = FakeMethod(text_to_return)
492+
493+
494+class TestMergeProposalMessage(unittest.TestCase):
495+
496+ def setUp(self):
497+ self.mail_to = "mp+1234@example.com"
498+ self.mail_from = "me@example.com"
499+ self.gpg_strategy = FakeGPGStrategy()
500+
501+ def test_merge_proposal_message_is_successful(self):
502+ successful = True
503+ message = MergeProposalMessage(self.mail_to, self.mail_from,
504+ successful=successful, gpg_strategy=self.gpg_strategy)
505+ self.assertTrue(message.successful)
506+ self.assertTrue("SUCCESS" in message.subject)
507+
508+ def test_merge_proposal_message_not_successful(self):
509+ successful = False
510+ message = MergeProposalMessage(self.mail_to, self.mail_from,
511+ successful=successful, gpg_strategy=self.gpg_strategy)
512+ self.assertFalse(message.successful)
513+ self.assertTrue("FAILURE" in message.subject)
514+
515+ def test_to_email(self):
516+ fake_signed_text = "signed msg body"
517+ gpg_strategy = FakeGPGStrategy(fake_signed_text)
518+
519+ message = MergeProposalMessage(self.mail_to, self.mail_from,
520+ successful=True, gpg_strategy=gpg_strategy)
521+ message = message.to_email()
522+ self.assertEqual(message._body, fake_signed_text)
523+
524+
525 class TestPQMRegexAcceptance(unittest.TestCase):
526 """Tests if the generated commit message is accepted by PQM regexes."""
527