Merge lp:~vexo/bzr/fix-1123460 into lp:bzr
- fix-1123460
- Merge into bzr.dev
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Richard Wilbur | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 6594 | ||||
Proposed branch: | lp:~vexo/bzr/fix-1123460 | ||||
Merge into: | lp:bzr | ||||
Diff against target: |
277 lines (+162/-45) 6 files modified
bzrlib/commit_signature_commands.py (+2/-1) bzrlib/log.py (+1/-1) bzrlib/tests/blackbox/__init__.py (+1/-0) bzrlib/tests/blackbox/test_sign_my_commits.py (+0/-43) bzrlib/tests/blackbox/test_verify_signatures.py (+124/-0) bzrlib/tests/test_log.py (+34/-0) |
||||
To merge this branch: | bzr merge lp:~vexo/bzr/fix-1123460 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Reagan Sanders (community) | Approve | ||
Richard Wilbur | Approve | ||
Review via email: mp+177290@code.launchpad.net |
Commit message
Fix bug LP: #1123460, verify-signature crashes on non ascii characters (and on -v)
Description of the change
Fixes bug LP: #1123460. This bug really wraps up two completely independent issues.
First, the issue with verify-signatures --verbose. We were attempting to pass an array of strings to write() directly. Changed to loop through the array and write them all.
Second, issue with UTF characters in GPG names. We were trying to stick the UTF strings from the GPG subsystem into plain ASCII format strings. Changed the format strings to be UTF as well.
After these fixes, bzr verify-signatures -v and bzr qlog both succeed on a test repository signed with a key containing a UTF-8 name.
vexo@anput:~/sign$ bzr verify-signatures -v
1 commits with valid signatures
ÀÇÐőbˆ Doe <email address hidden> signed 1 commit
0 commits with key now expired
1 commit with unknown key
Unknown key B2C03A8B signed 1 commit
0 commits not valid
0 commits not signed
vexo@anput:~/sign$ bzr qlog
vexo@anput:~/sign$
Reagan Sanders (vexo) wrote : | # |
Richard Wilbur (richard-wilbur) wrote : | # |
On Sun, Jul 28, 2013 at 12:06 PM, Reagan Sanders <email address hidden> wrote:
> Additional test example for the all-valid code path:
>
> vexo@anput:~/sign$ bzr verify-signatures -v
> All commits signed with verifiable keys
> ÀÇÐőbˆ Doe <email address hidden> signed 1 commit
> vexo@anput:~/sign$
> --
> https:/
> Your team bzr-core is requested to review the proposed merge of lp:~vexofp/bzr/fix-1123460 into lp:bzr.
Reagan,
I tried to review this merge request and I'm having trouble bringing
it up. It looks like some great fixes that would be nice to get in
the trunk.
I don't think the change to write out the array of strings in a loop
requires a test case--you fixed a flaw in the implementation! Nice
work.
How hard would it be to code up your 'verify signatures' test case?
Sincerely,
Richard
Reagan Sanders (vexo) wrote : | # |
I went ahead and added a couple basic test cases for verify-signatures --verbose, as it was completely uncovered previously and that didn't seem good. The minimal existing tests for verify-signatures were also oddly lumped in with sign-my-commits, so I split everything off into a new file.
After looking at it a bit more, the UTF-8 issue never actually had anything to do with verify-signatures, and the issue was really with generating the log, so I added the test case for it there. It seems like there might be a cleaner way of writing the test, but actually signing the test commit didn't seem to play nice with the way TestCaseWithTra
Richard Wilbur (richard-wilbur) wrote : | # |
Thanks for the great work! I sincerely appreciate the improved test coverage, and thank you for splitting out the verify-signatures test cases.
+1
Reagan Sanders (vexo) wrote : | # |
Don't know how I overlooked the merge request for lp:~krause/bzr/1037108, but it appears to be a subset of what I've done here. I'm not sure if there's merge-proposal equivalent for marking it as a duplicate or invalid, but noting it here so it can be handled appropriately if this gets landed.
https:/
Richard Wilbur (richard-wilbur) wrote : | # |
Thanks for noting the previous merge request. I will look into
marking it "duplicate" or "invalid", etc. Did you notice John
Meinel's suggestion to decode the utf-8 to unicode? Does that still
apply? I'm still learning about unicode vs. utf-8, etc.
Do we need to worry about unicode format string for
bzrlib/
of these format strings and the two format strings in
bzrlib/
Reagan Sanders (vexo) wrote : | # |
> Thanks for noting the previous merge request. I will look into
> marking it "duplicate" or "invalid", etc. Did you notice John
> Meinel's suggestion to decode the utf-8 to unicode? Does that still
> apply? I'm still learning about unicode vs. utf-8, etc.
I don't *think* so, as pygpgme was already nice enough to do what John was suggesting might be required.
result[1] is created at gpg.py:295 as the concatenation of key.uids[0].name and key.uids[0].email, where key is self.context.
http://
> Do we need to worry about unicode format string for
> bzrlib/
No, if the key is missing, that string will always be the hexadecimal ID for the string (eg. 1F4AAD09), since that would be the only thing we know at that point. Similar to the above analysis, the value here is derived from a call to PyUnicode_
http://
> How about translations
> of these format strings and the two format strings in
> bzrlib/
As far as I can tell, nothing else that shows up in the *output* of the log seems to be translated, though the error messages are. Either way, I think that'd go beyond the scope of this bug fix and into adding new functionality.
Now, a valid question might be why format_
Richard Wilbur (richard-wilbur) wrote : | # |
I reviewed https:/
I think this ready for merging. Don't you?
Reagan Sanders (vexo) wrote : | # |
Agree. (Good thing too, with how my free time has been lately... :/ )
Richard Wilbur (richard-wilbur) wrote : | # |
sent to pqm by email
Richard Wilbur (richard-wilbur) wrote : | # |
sent to pqm by email
Richard Wilbur (richard-wilbur) wrote : | # |
Reagan, I finally got my bzr and gpg configuration properly setup and the folks on launchpad setup the authentication configuration for pqm so I am able to merge to trunk. Many thanks to John Meinel and Vincent Ladeuil for their advice and assistance.
Thus, this branch finally landed on trunk!
Preview Diff
1 | === modified file 'bzrlib/commit_signature_commands.py' | |||
2 | --- bzrlib/commit_signature_commands.py 2012-03-11 16:51:49 +0000 | |||
3 | +++ bzrlib/commit_signature_commands.py 2013-08-11 20:38:13 +0000 | |||
4 | @@ -165,7 +165,8 @@ | |||
5 | 165 | if all_verifiable: | 165 | if all_verifiable: |
6 | 166 | write(gettext("All commits signed with verifiable keys")) | 166 | write(gettext("All commits signed with verifiable keys")) |
7 | 167 | if verbose: | 167 | if verbose: |
9 | 168 | write(gpg.verbose_valid_message(result)) | 168 | for message in gpg.verbose_valid_message(result): |
10 | 169 | write_verbose(message) | ||
11 | 169 | return 0 | 170 | return 0 |
12 | 170 | else: | 171 | else: |
13 | 171 | write(gpg.valid_commits_message(count)) | 172 | write(gpg.valid_commits_message(count)) |
14 | 172 | 173 | ||
15 | === modified file 'bzrlib/log.py' | |||
16 | --- bzrlib/log.py 2012-03-14 11:30:42 +0000 | |||
17 | +++ bzrlib/log.py 2013-08-11 20:38:13 +0000 | |||
18 | @@ -334,7 +334,7 @@ | |||
19 | 334 | gpg_strategy = gpg.GPGStrategy(None) | 334 | gpg_strategy = gpg.GPGStrategy(None) |
20 | 335 | result = repo.verify_revision_signature(rev_id, gpg_strategy) | 335 | result = repo.verify_revision_signature(rev_id, gpg_strategy) |
21 | 336 | if result[0] == gpg.SIGNATURE_VALID: | 336 | if result[0] == gpg.SIGNATURE_VALID: |
23 | 337 | return "valid signature from {0}".format(result[1]) | 337 | return u"valid signature from {0}".format(result[1]) |
24 | 338 | if result[0] == gpg.SIGNATURE_KEY_MISSING: | 338 | if result[0] == gpg.SIGNATURE_KEY_MISSING: |
25 | 339 | return "unknown key {0}".format(result[1]) | 339 | return "unknown key {0}".format(result[1]) |
26 | 340 | if result[0] == gpg.SIGNATURE_NOT_VALID: | 340 | if result[0] == gpg.SIGNATURE_NOT_VALID: |
27 | 341 | 341 | ||
28 | === modified file 'bzrlib/tests/blackbox/__init__.py' | |||
29 | --- bzrlib/tests/blackbox/__init__.py 2012-09-08 13:26:18 +0000 | |||
30 | +++ bzrlib/tests/blackbox/__init__.py 2013-08-11 20:38:13 +0000 | |||
31 | @@ -118,6 +118,7 @@ | |||
32 | 118 | 'test_shell_complete', | 118 | 'test_shell_complete', |
33 | 119 | 'test_shelve', | 119 | 'test_shelve', |
34 | 120 | 'test_sign_my_commits', | 120 | 'test_sign_my_commits', |
35 | 121 | 'test_verify_signatures', | ||
36 | 121 | 'test_split', | 122 | 'test_split', |
37 | 122 | 'test_status', | 123 | 'test_status', |
38 | 123 | 'test_switch', | 124 | 'test_switch', |
39 | 124 | 125 | ||
40 | === modified file 'bzrlib/tests/blackbox/test_sign_my_commits.py' | |||
41 | --- bzrlib/tests/blackbox/test_sign_my_commits.py 2012-03-11 16:51:49 +0000 | |||
42 | +++ bzrlib/tests/blackbox/test_sign_my_commits.py 2013-08-11 20:38:13 +0000 | |||
43 | @@ -116,29 +116,6 @@ | |||
44 | 116 | self.assertUnsigned(repo, 'D') | 116 | self.assertUnsigned(repo, 'D') |
45 | 117 | self.assertUnsigned(repo, 'E') | 117 | self.assertUnsigned(repo, 'E') |
46 | 118 | 118 | ||
47 | 119 | def test_verify_commits(self): | ||
48 | 120 | wt = self.setup_tree() | ||
49 | 121 | self.monkey_patch_gpg() | ||
50 | 122 | self.run_bzr('sign-my-commits') | ||
51 | 123 | out = self.run_bzr('verify-signatures', retcode=1) | ||
52 | 124 | self.assertEquals(('4 commits with valid signatures\n' | ||
53 | 125 | '0 commits with key now expired\n' | ||
54 | 126 | '0 commits with unknown keys\n' | ||
55 | 127 | '0 commits not valid\n' | ||
56 | 128 | '1 commit not signed\n', ''), out) | ||
57 | 129 | |||
58 | 130 | def test_verify_commits_acceptable_key(self): | ||
59 | 131 | wt = self.setup_tree() | ||
60 | 132 | self.monkey_patch_gpg() | ||
61 | 133 | self.run_bzr('sign-my-commits') | ||
62 | 134 | out = self.run_bzr(['verify-signatures', '--acceptable-keys=foo,bar'], | ||
63 | 135 | retcode=1) | ||
64 | 136 | self.assertEquals(('4 commits with valid signatures\n' | ||
65 | 137 | '0 commits with key now expired\n' | ||
66 | 138 | '0 commits with unknown keys\n' | ||
67 | 139 | '0 commits not valid\n' | ||
68 | 140 | '1 commit not signed\n', ''), out) | ||
69 | 141 | |||
70 | 142 | 119 | ||
71 | 143 | class TestSmartServerSignMyCommits(tests.TestCaseWithTransport): | 120 | class TestSmartServerSignMyCommits(tests.TestCaseWithTransport): |
72 | 144 | 121 | ||
73 | @@ -168,23 +145,3 @@ | |||
74 | 168 | self.assertLength(15, self.hpss_calls) | 145 | self.assertLength(15, self.hpss_calls) |
75 | 169 | self.assertLength(1, self.hpss_connections) | 146 | self.assertLength(1, self.hpss_connections) |
76 | 170 | self.assertThat(self.hpss_calls, ContainsNoVfsCalls) | 147 | self.assertThat(self.hpss_calls, ContainsNoVfsCalls) |
77 | 171 | |||
78 | 172 | def test_verify_commits(self): | ||
79 | 173 | self.setup_smart_server_with_call_log() | ||
80 | 174 | t = self.make_branch_and_tree('branch') | ||
81 | 175 | self.build_tree_contents([('branch/foo', 'thecontents')]) | ||
82 | 176 | t.add("foo") | ||
83 | 177 | t.commit("message") | ||
84 | 178 | self.monkey_patch_gpg() | ||
85 | 179 | out, err = self.run_bzr(['sign-my-commits', self.get_url('branch')]) | ||
86 | 180 | self.reset_smart_call_log() | ||
87 | 181 | self.run_bzr('sign-my-commits') | ||
88 | 182 | out = self.run_bzr(['verify-signatures', self.get_url('branch')]) | ||
89 | 183 | # This figure represent the amount of work to perform this use case. It | ||
90 | 184 | # is entirely ok to reduce this number if a test fails due to rpc_count | ||
91 | 185 | # being too low. If rpc_count increases, more network roundtrips have | ||
92 | 186 | # become necessary for this use case. Please do not adjust this number | ||
93 | 187 | # upwards without agreement from bzr's network support maintainers. | ||
94 | 188 | self.assertLength(10, self.hpss_calls) | ||
95 | 189 | self.assertLength(1, self.hpss_connections) | ||
96 | 190 | self.assertThat(self.hpss_calls, ContainsNoVfsCalls) | ||
97 | 191 | 148 | ||
98 | === added file 'bzrlib/tests/blackbox/test_verify_signatures.py' | |||
99 | --- bzrlib/tests/blackbox/test_verify_signatures.py 1970-01-01 00:00:00 +0000 | |||
100 | +++ bzrlib/tests/blackbox/test_verify_signatures.py 2013-08-11 20:38:13 +0000 | |||
101 | @@ -0,0 +1,124 @@ | |||
102 | 1 | # Copyright (C) 2005 Canonical Ltd | ||
103 | 2 | # -*- coding: utf-8 -*- | ||
104 | 3 | # | ||
105 | 4 | # This program is free software; you can redistribute it and/or modify | ||
106 | 5 | # it under the terms of the GNU General Public License as published by | ||
107 | 6 | # the Free Software Foundation; either version 2 of the License, or | ||
108 | 7 | # (at your option) any later version. | ||
109 | 8 | # | ||
110 | 9 | # This program is distributed in the hope that it will be useful, | ||
111 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
112 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
113 | 12 | # GNU General Public License for more details. | ||
114 | 13 | # | ||
115 | 14 | # You should have received a copy of the GNU General Public License | ||
116 | 15 | # along with this program; if not, write to the Free Software | ||
117 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
118 | 17 | |||
119 | 18 | """Black-box tests for bzr verify-signatures.""" | ||
120 | 19 | |||
121 | 20 | from bzrlib import ( | ||
122 | 21 | gpg, | ||
123 | 22 | tests, | ||
124 | 23 | ) | ||
125 | 24 | from bzrlib.tests.matchers import ContainsNoVfsCalls | ||
126 | 25 | |||
127 | 26 | |||
128 | 27 | class TestVerifySignatures(tests.TestCaseWithTransport): | ||
129 | 28 | |||
130 | 29 | def monkey_patch_gpg(self): | ||
131 | 30 | """Monkey patch the gpg signing strategy to be a loopback. | ||
132 | 31 | |||
133 | 32 | This also registers the cleanup, so that we will revert to | ||
134 | 33 | the original gpg strategy when done. | ||
135 | 34 | """ | ||
136 | 35 | # monkey patch gpg signing mechanism | ||
137 | 36 | self.overrideAttr(gpg, 'GPGStrategy', gpg.LoopbackGPGStrategy) | ||
138 | 37 | |||
139 | 38 | def setup_tree(self, location='.'): | ||
140 | 39 | wt = self.make_branch_and_tree(location) | ||
141 | 40 | wt.commit("base A", allow_pointless=True, rev_id='A') | ||
142 | 41 | wt.commit("base B", allow_pointless=True, rev_id='B') | ||
143 | 42 | wt.commit("base C", allow_pointless=True, rev_id='C') | ||
144 | 43 | wt.commit("base D", allow_pointless=True, rev_id='D', | ||
145 | 44 | committer='Alternate <alt@foo.com>') | ||
146 | 45 | wt.add_parent_tree_id("aghost") | ||
147 | 46 | wt.commit("base E", allow_pointless=True, rev_id='E') | ||
148 | 47 | return wt | ||
149 | 48 | |||
150 | 49 | def test_verify_signatures(self): | ||
151 | 50 | wt = self.setup_tree() | ||
152 | 51 | self.monkey_patch_gpg() | ||
153 | 52 | self.run_bzr('sign-my-commits') | ||
154 | 53 | out = self.run_bzr('verify-signatures', retcode=1) | ||
155 | 54 | self.assertEquals(('4 commits with valid signatures\n' | ||
156 | 55 | '0 commits with key now expired\n' | ||
157 | 56 | '0 commits with unknown keys\n' | ||
158 | 57 | '0 commits not valid\n' | ||
159 | 58 | '1 commit not signed\n', ''), out) | ||
160 | 59 | |||
161 | 60 | def test_verify_signatures_acceptable_key(self): | ||
162 | 61 | wt = self.setup_tree() | ||
163 | 62 | self.monkey_patch_gpg() | ||
164 | 63 | self.run_bzr('sign-my-commits') | ||
165 | 64 | out = self.run_bzr(['verify-signatures', '--acceptable-keys=foo,bar'], | ||
166 | 65 | retcode=1) | ||
167 | 66 | self.assertEquals(('4 commits with valid signatures\n' | ||
168 | 67 | '0 commits with key now expired\n' | ||
169 | 68 | '0 commits with unknown keys\n' | ||
170 | 69 | '0 commits not valid\n' | ||
171 | 70 | '1 commit not signed\n', ''), out) | ||
172 | 71 | |||
173 | 72 | def test_verify_signatures_verbose(self): | ||
174 | 73 | wt = self.setup_tree() | ||
175 | 74 | self.monkey_patch_gpg() | ||
176 | 75 | self.run_bzr('sign-my-commits') | ||
177 | 76 | out = self.run_bzr('verify-signatures --verbose', retcode=1) | ||
178 | 77 | self.assertEquals(('4 commits with valid signatures\n' | ||
179 | 78 | ' None signed 4 commits\n' | ||
180 | 79 | '0 commits with key now expired\n' | ||
181 | 80 | '0 commits with unknown keys\n' | ||
182 | 81 | '0 commits not valid\n' | ||
183 | 82 | '1 commit not signed\n' | ||
184 | 83 | ' 1 commit by author Alternate <alt@foo.com>\n', ''), out) | ||
185 | 84 | |||
186 | 85 | def test_verify_signatures_verbose_all_valid(self): | ||
187 | 86 | wt = self.setup_tree() | ||
188 | 87 | self.monkey_patch_gpg() | ||
189 | 88 | self.run_bzr('sign-my-commits') | ||
190 | 89 | self.run_bzr(['sign-my-commits', '.', 'Alternate <alt@foo.com>']) | ||
191 | 90 | out = self.run_bzr('verify-signatures --verbose') | ||
192 | 91 | self.assertEquals(('All commits signed with verifiable keys\n' | ||
193 | 92 | ' None signed 5 commits\n', ''), out) | ||
194 | 93 | |||
195 | 94 | |||
196 | 95 | class TestSmartServerVerifySignatures(tests.TestCaseWithTransport): | ||
197 | 96 | |||
198 | 97 | def monkey_patch_gpg(self): | ||
199 | 98 | """Monkey patch the gpg signing strategy to be a loopback. | ||
200 | 99 | |||
201 | 100 | This also registers the cleanup, so that we will revert to | ||
202 | 101 | the original gpg strategy when done. | ||
203 | 102 | """ | ||
204 | 103 | # monkey patch gpg signing mechanism | ||
205 | 104 | self.overrideAttr(gpg, 'GPGStrategy', gpg.LoopbackGPGStrategy) | ||
206 | 105 | |||
207 | 106 | def test_verify_signatures(self): | ||
208 | 107 | self.setup_smart_server_with_call_log() | ||
209 | 108 | t = self.make_branch_and_tree('branch') | ||
210 | 109 | self.build_tree_contents([('branch/foo', 'thecontents')]) | ||
211 | 110 | t.add("foo") | ||
212 | 111 | t.commit("message") | ||
213 | 112 | self.monkey_patch_gpg() | ||
214 | 113 | out, err = self.run_bzr(['sign-my-commits', self.get_url('branch')]) | ||
215 | 114 | self.reset_smart_call_log() | ||
216 | 115 | self.run_bzr('sign-my-commits') | ||
217 | 116 | out = self.run_bzr(['verify-signatures', self.get_url('branch')]) | ||
218 | 117 | # This figure represent the amount of work to perform this use case. It | ||
219 | 118 | # is entirely ok to reduce this number if a test fails due to rpc_count | ||
220 | 119 | # being too low. If rpc_count increases, more network roundtrips have | ||
221 | 120 | # become necessary for this use case. Please do not adjust this number | ||
222 | 121 | # upwards without agreement from bzr's network support maintainers. | ||
223 | 122 | self.assertLength(10, self.hpss_calls) | ||
224 | 123 | self.assertLength(1, self.hpss_connections) | ||
225 | 124 | self.assertThat(self.hpss_calls, ContainsNoVfsCalls) | ||
226 | 0 | 125 | ||
227 | === modified file 'bzrlib/tests/test_log.py' | |||
228 | --- bzrlib/tests/test_log.py 2012-08-04 14:27:47 +0000 | |||
229 | +++ bzrlib/tests/test_log.py 2013-08-11 20:38:13 +0000 | |||
230 | @@ -25,6 +25,8 @@ | |||
231 | 25 | revision, | 25 | revision, |
232 | 26 | revisionspec, | 26 | revisionspec, |
233 | 27 | tests, | 27 | tests, |
234 | 28 | gpg, | ||
235 | 29 | trace, | ||
236 | 28 | ) | 30 | ) |
237 | 29 | 31 | ||
238 | 30 | 32 | ||
239 | @@ -281,6 +283,38 @@ | |||
240 | 281 | self.checkDelta(logentry.delta, added=['file1', 'file2']) | 283 | self.checkDelta(logentry.delta, added=['file1', 'file2']) |
241 | 282 | 284 | ||
242 | 283 | 285 | ||
243 | 286 | class TestFormatSignatureValidity(tests.TestCaseWithTransport): | ||
244 | 287 | class UTFLoopbackGPGStrategy(gpg.LoopbackGPGStrategy): | ||
245 | 288 | def verify(self, content, testament): | ||
246 | 289 | return (gpg.SIGNATURE_VALID, | ||
247 | 290 | u'UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>') | ||
248 | 291 | |||
249 | 292 | def has_signature_for_revision_id(self, revision_id): | ||
250 | 293 | return True | ||
251 | 294 | |||
252 | 295 | def get_signature_text(self, revision_id): | ||
253 | 296 | return '' | ||
254 | 297 | |||
255 | 298 | def test_format_signature_validity_utf(self): | ||
256 | 299 | """Check that GPG signatures containing UTF-8 names are formatted | ||
257 | 300 | correctly.""" | ||
258 | 301 | # Monkey patch to use our UTF-8 generating GPGStrategy | ||
259 | 302 | self.overrideAttr(gpg, 'GPGStrategy', self.UTFLoopbackGPGStrategy) | ||
260 | 303 | wt = self.make_branch_and_tree('.') | ||
261 | 304 | revid = wt.commit('empty commit') | ||
262 | 305 | repo = wt.branch.repository | ||
263 | 306 | # Monkey patch out checking if this rev is actually signed, since we | ||
264 | 307 | # can't sign it without a heavier TestCase and LoopbackGPGStrategy | ||
265 | 308 | # doesn't care anyways. | ||
266 | 309 | self.overrideAttr(repo, 'has_signature_for_revision_id', | ||
267 | 310 | self.has_signature_for_revision_id) | ||
268 | 311 | self.overrideAttr(repo, 'get_signature_text', self.get_signature_text) | ||
269 | 312 | out = log.format_signature_validity(revid, repo) | ||
270 | 313 | self.assertEqual( | ||
271 | 314 | u'valid signature from UTF8 Test \xa1\xb1\xc1\xd1\xe1\xf1 <jrandom@example.com>', | ||
272 | 315 | out) | ||
273 | 316 | |||
274 | 317 | |||
275 | 284 | class TestShortLogFormatter(TestCaseForLogFormatter): | 318 | class TestShortLogFormatter(TestCaseForLogFormatter): |
276 | 285 | 319 | ||
277 | 286 | def test_trailing_newlines(self): | 320 | def test_trailing_newlines(self): |
Additional test example for the all-valid code path:
vexo@anput:~/sign$ bzr verify-signatures -v
All commits signed with verifiable keys
ÀÇÐőbˆ Doe <email address hidden> signed 1 commit
vexo@anput:~/sign$