Merge lp:~jelmer/launchpad/621778-parse-homepage-field into lp:launchpad/db-devel
- 621778-parse-homepage-field
- Merge into db-devel
Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merged at revision: | 9738 |
Proposed branch: | lp:~jelmer/launchpad/621778-parse-homepage-field |
Merge into: | lp:launchpad/db-devel |
Prerequisite: | lp:~jelmer/launchpad/621778-homepage-field |
Diff against target: |
296 lines (+52/-26) 6 files modified
lib/lp/archiveuploader/dscfile.py (+2/-0) lib/lp/archiveuploader/nascentuploadfile.py (+14/-14) lib/lp/archiveuploader/tests/test_nascentuploadfile.py (+29/-7) lib/lp/registry/browser/product.py (+3/-1) lib/lp/registry/stories/product/xx-product-add.txt (+2/-2) lib/lp/soyuz/tests/test_publishing.py (+2/-2) |
To merge this branch: | bzr merge lp:~jelmer/launchpad/621778-parse-homepage-field |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | code | Approve | |
Review via email: mp+33334@code.launchpad.net |
Commit message
Parse Homepage field and store it in SourcePackageRe
Description of the change
This adds support for parsing the Homepage field from SourcePackageRe
Jelmer Vernooij (jelmer) wrote : | # |
> Looks good. Just two notes:
>
> In line 49 of the diff: don't bother documenting tests as "Test that…" That's
> obvious from them being test methods. Just takes up space.
>
> In line 94 of the diff: badly formatted line break. Try putting both
> arguments to the assertEqual together on the line after the invocation.
>
> Please mention the "make lint" output in your merge proposals so that your
> reviewer doesn't need to worry about whether they have to play human linter.
> If there's too much pre-existing lint, well, throw in some drive-by cleanups!
Thanks. I've fixed the issues you've mentioned and cleaned up the lint in the files I've changed.
Is there an easy way to lint all affected files, other than through the lpreview-body plugin?
Preview Diff
1 | === modified file 'lib/lp/archiveuploader/dscfile.py' | |||
2 | --- lib/lp/archiveuploader/dscfile.py 2010-08-30 15:00:23 +0000 | |||
3 | +++ lib/lp/archiveuploader/dscfile.py 2010-09-02 11:38:52 +0000 | |||
4 | @@ -250,6 +250,7 @@ | |||
5 | 250 | "format", | 250 | "format", |
6 | 251 | "standards-version", | 251 | "standards-version", |
7 | 252 | "filecontents", | 252 | "filecontents", |
8 | 253 | "homepage", | ||
9 | 253 | ])) | 254 | ])) |
10 | 254 | 255 | ||
11 | 255 | # Note that files is actually only set inside verify(). | 256 | # Note that files is actually only set inside verify(). |
12 | @@ -707,6 +708,7 @@ | |||
13 | 707 | architecturehintlist=encoded.get('architecture', ''), | 708 | architecturehintlist=encoded.get('architecture', ''), |
14 | 708 | creator=self.changes.changed_by['person'], | 709 | creator=self.changes.changed_by['person'], |
15 | 709 | urgency=self.changes.converted_urgency, | 710 | urgency=self.changes.converted_urgency, |
16 | 711 | homepage=encoded.get('homepage'), | ||
17 | 710 | dsc=encoded['filecontents'], | 712 | dsc=encoded['filecontents'], |
18 | 711 | dscsigningkey=self.signingkey, | 713 | dscsigningkey=self.signingkey, |
19 | 712 | dsc_maintainer_rfc822=encoded['maintainer'], | 714 | dsc_maintainer_rfc822=encoded['maintainer'], |
20 | 713 | 715 | ||
21 | === modified file 'lib/lp/archiveuploader/nascentuploadfile.py' | |||
22 | --- lib/lp/archiveuploader/nascentuploadfile.py 2010-08-30 15:00:23 +0000 | |||
23 | +++ lib/lp/archiveuploader/nascentuploadfile.py 2010-09-02 11:38:52 +0000 | |||
24 | @@ -60,9 +60,11 @@ | |||
25 | 60 | 60 | ||
26 | 61 | apt_pkg.InitSystem() | 61 | apt_pkg.InitSystem() |
27 | 62 | 62 | ||
28 | 63 | |||
29 | 63 | class UploadError(Exception): | 64 | class UploadError(Exception): |
30 | 64 | """All upload errors are returned in this form.""" | 65 | """All upload errors are returned in this form.""" |
31 | 65 | 66 | ||
32 | 67 | |||
33 | 66 | class UploadWarning(Warning): | 68 | class UploadWarning(Warning): |
34 | 67 | """All upload warnings are returned in this form.""" | 69 | """All upload warnings are returned in this form.""" |
35 | 68 | 70 | ||
36 | @@ -72,6 +74,7 @@ | |||
37 | 72 | 74 | ||
38 | 73 | This was taken from jennifer in the DAK suite. | 75 | This was taken from jennifer in the DAK suite. |
39 | 74 | """ | 76 | """ |
40 | 77 | |||
41 | 75 | def __init__(self, future_cutoff, past_cutoff): | 78 | def __init__(self, future_cutoff, past_cutoff): |
42 | 76 | """Setup timestamp limits """ | 79 | """Setup timestamp limits """ |
43 | 77 | self.reset() | 80 | self.reset() |
44 | @@ -126,7 +129,7 @@ | |||
45 | 126 | ".deb": "application/x-debian-package", | 129 | ".deb": "application/x-debian-package", |
46 | 127 | ".udeb": "application/x-micro-debian-package", | 130 | ".udeb": "application/x-micro-debian-package", |
47 | 128 | ".diff.gz": "application/gzipped-patch", | 131 | ".diff.gz": "application/gzipped-patch", |
49 | 129 | ".tar.gz": "application/gzipped-tar" | 132 | ".tar.gz": "application/gzipped-tar", |
50 | 130 | } | 133 | } |
51 | 131 | 134 | ||
52 | 132 | def __init__(self, filepath, digest, size, component_and_section, | 135 | def __init__(self, filepath, digest, size, component_and_section, |
53 | @@ -146,7 +149,6 @@ | |||
54 | 146 | # | 149 | # |
55 | 147 | # Helpers used quen inserting into queue | 150 | # Helpers used quen inserting into queue |
56 | 148 | # | 151 | # |
57 | 149 | |||
58 | 150 | @property | 152 | @property |
59 | 151 | def content_type(self): | 153 | def content_type(self): |
60 | 152 | """The content type for this file. | 154 | """The content type for this file. |
61 | @@ -172,7 +174,6 @@ | |||
62 | 172 | """Return the NascentUpload filename.""" | 174 | """Return the NascentUpload filename.""" |
63 | 173 | return os.path.dirname(self.filepath) | 175 | return os.path.dirname(self.filepath) |
64 | 174 | 176 | ||
65 | 175 | |||
66 | 176 | @property | 177 | @property |
67 | 177 | def exists_on_disk(self): | 178 | def exists_on_disk(self): |
68 | 178 | """Whether or not the file is present on disk.""" | 179 | """Whether or not the file is present on disk.""" |
69 | @@ -181,7 +182,6 @@ | |||
70 | 181 | # | 182 | # |
71 | 182 | # DB storage helpers | 183 | # DB storage helpers |
72 | 183 | # | 184 | # |
73 | 184 | |||
74 | 185 | def storeInDatabase(self): | 185 | def storeInDatabase(self): |
75 | 186 | """Implement this to store this representation in the database.""" | 186 | """Implement this to store this representation in the database.""" |
76 | 187 | raise NotImplementedError | 187 | raise NotImplementedError |
77 | @@ -189,7 +189,6 @@ | |||
78 | 189 | # | 189 | # |
79 | 190 | # Verification | 190 | # Verification |
80 | 191 | # | 191 | # |
81 | 192 | |||
82 | 193 | def verify(self): | 192 | def verify(self): |
83 | 194 | """Implemented locally. | 193 | """Implemented locally. |
84 | 195 | 194 | ||
85 | @@ -267,7 +266,7 @@ | |||
86 | 267 | 'raw-ddtp-tarball': PackageUploadCustomFormat.DDTP_TARBALL, | 266 | 'raw-ddtp-tarball': PackageUploadCustomFormat.DDTP_TARBALL, |
87 | 268 | 'raw-translations-static': | 267 | 'raw-translations-static': |
88 | 269 | PackageUploadCustomFormat.STATIC_TRANSLATIONS, | 268 | PackageUploadCustomFormat.STATIC_TRANSLATIONS, |
90 | 270 | 'raw-meta-data' : | 269 | 'raw-meta-data': |
91 | 271 | PackageUploadCustomFormat.META_DATA, | 270 | PackageUploadCustomFormat.META_DATA, |
92 | 272 | } | 271 | } |
93 | 273 | 272 | ||
94 | @@ -408,6 +407,7 @@ | |||
95 | 408 | "Section", | 407 | "Section", |
96 | 409 | "Maintainer", | 408 | "Maintainer", |
97 | 410 | "Source", | 409 | "Source", |
98 | 410 | "Homepage", | ||
99 | 411 | ])) | 411 | ])) |
100 | 412 | 412 | ||
101 | 413 | # Map priorities to their dbschema valuesa | 413 | # Map priorities to their dbschema valuesa |
102 | @@ -419,7 +419,7 @@ | |||
103 | 419 | "standard": PackagePublishingPriority.STANDARD, | 419 | "standard": PackagePublishingPriority.STANDARD, |
104 | 420 | "optional": PackagePublishingPriority.OPTIONAL, | 420 | "optional": PackagePublishingPriority.OPTIONAL, |
105 | 421 | "extra": PackagePublishingPriority.EXTRA, | 421 | "extra": PackagePublishingPriority.EXTRA, |
107 | 422 | "-": PackagePublishingPriority.EXTRA | 422 | "-": PackagePublishingPriority.EXTRA, |
108 | 423 | } | 423 | } |
109 | 424 | 424 | ||
110 | 425 | # These are divined when parsing the package file in verify(), and | 425 | # These are divined when parsing the package file in verify(), and |
111 | @@ -452,7 +452,6 @@ | |||
112 | 452 | # | 452 | # |
113 | 453 | # Useful properties. | 453 | # Useful properties. |
114 | 454 | # | 454 | # |
115 | 455 | |||
116 | 456 | @property | 455 | @property |
117 | 457 | def is_archindep(self): | 456 | def is_archindep(self): |
118 | 458 | """Check if the binary is targeted to architecture 'all'. | 457 | """Check if the binary is targeted to architecture 'all'. |
119 | @@ -753,15 +752,15 @@ | |||
120 | 753 | % (self.filename, error)) | 752 | % (self.filename, error)) |
121 | 754 | 753 | ||
122 | 755 | 754 | ||
127 | 756 | # | 755 | # |
128 | 757 | # Database relationship methods | 756 | # Database relationship methods |
129 | 758 | # | 757 | # |
126 | 759 | |||
130 | 760 | def findSourcePackageRelease(self): | 758 | def findSourcePackageRelease(self): |
131 | 761 | """Return the respective ISourcePackagRelease for this binary upload. | 759 | """Return the respective ISourcePackagRelease for this binary upload. |
132 | 762 | 760 | ||
133 | 763 | It inspect publication in the targeted DistroSeries and also the | 761 | It inspect publication in the targeted DistroSeries and also the |
135 | 764 | ACCEPTED queue for sources matching stored (source_name, source_version). | 762 | ACCEPTED queue for sources matching stored |
136 | 763 | (source_name, source_version). | ||
137 | 765 | 764 | ||
138 | 766 | It raises UploadError if the source was not found. | 765 | It raises UploadError if the source was not found. |
139 | 767 | 766 | ||
140 | @@ -897,7 +896,7 @@ | |||
141 | 897 | 896 | ||
142 | 898 | is_essential = encoded.get('Essential', '').lower() == 'yes' | 897 | is_essential = encoded.get('Essential', '').lower() == 'yes' |
143 | 899 | architecturespecific = not self.is_archindep | 898 | architecturespecific = not self.is_archindep |
145 | 900 | installedsize = int(self.control.get('Installed-Size','0')) | 899 | installedsize = int(self.control.get('Installed-Size', '0')) |
146 | 901 | binary_name = getUtility( | 900 | binary_name = getUtility( |
147 | 902 | IBinaryPackageNameSet).getOrCreateByName(self.package) | 901 | IBinaryPackageNameSet).getOrCreateByName(self.package) |
148 | 903 | 902 | ||
149 | @@ -929,6 +928,7 @@ | |||
150 | 929 | pre_depends=encoded.get('Pre-Depends', ''), | 928 | pre_depends=encoded.get('Pre-Depends', ''), |
151 | 930 | enhances=encoded.get('Enhances', ''), | 929 | enhances=encoded.get('Enhances', ''), |
152 | 931 | breaks=encoded.get('Breaks', ''), | 930 | breaks=encoded.get('Breaks', ''), |
153 | 931 | homepage=encoded.get('Homepage'), | ||
154 | 932 | essential=is_essential, | 932 | essential=is_essential, |
155 | 933 | installedsize=installedsize, | 933 | installedsize=installedsize, |
156 | 934 | architecturespecific=architecturespecific, | 934 | architecturespecific=architecturespecific, |
157 | 935 | 935 | ||
158 | === modified file 'lib/lp/archiveuploader/tests/test_nascentuploadfile.py' | |||
159 | --- lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2010-08-27 02:21:01 +0000 | |||
160 | +++ lib/lp/archiveuploader/tests/test_nascentuploadfile.py 2010-09-02 11:38:52 +0000 | |||
161 | @@ -176,7 +176,7 @@ | |||
162 | 176 | self.assertEquals("dpkg, bzr", release.builddepends) | 176 | self.assertEquals("dpkg, bzr", release.builddepends) |
163 | 177 | 177 | ||
164 | 178 | def test_user_defined_fields(self): | 178 | def test_user_defined_fields(self): |
166 | 179 | # Test that storeInDatabase updates user_defined_fields. | 179 | # storeInDatabase updates user_defined_fields. |
167 | 180 | dsc = self.getBaseDsc() | 180 | dsc = self.getBaseDsc() |
168 | 181 | dsc["Python-Version"] = "2.5" | 181 | dsc["Python-Version"] = "2.5" |
169 | 182 | changes = self.getBaseChanges() | 182 | changes = self.getBaseChanges() |
170 | @@ -190,6 +190,19 @@ | |||
171 | 190 | self.assertEquals( | 190 | self.assertEquals( |
172 | 191 | [["python-version", u"2.5"]], release.user_defined_fields) | 191 | [["python-version", u"2.5"]], release.user_defined_fields) |
173 | 192 | 192 | ||
174 | 193 | def test_homepage(self): | ||
175 | 194 | # storeInDatabase updates homepage. | ||
176 | 195 | dsc = self.getBaseDsc() | ||
177 | 196 | dsc["Homepage"] = "http://samba.org/~jelmer/bzr" | ||
178 | 197 | changes = self.getBaseChanges() | ||
179 | 198 | uploadfile = self.createDSCFile( | ||
180 | 199 | "foo.dsc", dsc, "main/net", "extra", "dulwich", "0.42", | ||
181 | 200 | self.createChangesFile("foo.changes", changes)) | ||
182 | 201 | uploadfile.changelog = "DUMMY" | ||
183 | 202 | uploadfile.files = [] | ||
184 | 203 | release = uploadfile.storeInDatabase(None) | ||
185 | 204 | self.assertEquals(u"http://samba.org/~jelmer/bzr", release.homepage) | ||
186 | 205 | |||
187 | 193 | 206 | ||
188 | 194 | class DebBinaryUploadFileTests(PackageUploadFileTestCase): | 207 | class DebBinaryUploadFileTests(PackageUploadFileTestCase): |
189 | 195 | """Tests for DebBinaryUploadFile.""" | 208 | """Tests for DebBinaryUploadFile.""" |
190 | @@ -211,7 +224,7 @@ | |||
191 | 211 | "Homepage": "http://samba.org/~jelmer/dulwich", | 224 | "Homepage": "http://samba.org/~jelmer/dulwich", |
192 | 212 | "Description": "Pure-python Git library\n" | 225 | "Description": "Pure-python Git library\n" |
193 | 213 | "Dulwich is a Python implementation of the file formats and " | 226 | "Dulwich is a Python implementation of the file formats and " |
195 | 214 | "protocols" | 227 | "protocols", |
196 | 215 | } | 228 | } |
197 | 216 | 229 | ||
198 | 217 | def createDebBinaryUploadFile(self, filename, component_and_section, | 230 | def createDebBinaryUploadFile(self, filename, component_and_section, |
199 | @@ -270,10 +283,7 @@ | |||
200 | 270 | build = self.factory.makeBinaryPackageBuild() | 283 | build = self.factory.makeBinaryPackageBuild() |
201 | 271 | bpr = uploadfile.storeInDatabase(build) | 284 | bpr = uploadfile.storeInDatabase(build) |
202 | 272 | self.assertEquals( | 285 | self.assertEquals( |
207 | 273 | [ | 286 | [[u"Python-Version", u"2.5"]], bpr.user_defined_fields) |
204 | 274 | [u"Homepage", u"http://samba.org/~jelmer/dulwich"], | ||
205 | 275 | [u"Python-Version", u"2.5"] | ||
206 | 276 | ], bpr.user_defined_fields) | ||
208 | 277 | 287 | ||
209 | 278 | def test_user_defined_fields_newlines(self): | 288 | def test_user_defined_fields_newlines(self): |
210 | 279 | # storeInDatabase stores user defined fields and keeps newlines. | 289 | # storeInDatabase stores user defined fields and keeps newlines. |
211 | @@ -288,5 +298,17 @@ | |||
212 | 288 | self.assertEquals( | 298 | self.assertEquals( |
213 | 289 | [ | 299 | [ |
214 | 290 | [u"RandomData", u"Foo\nbar\nbla\n"], | 300 | [u"RandomData", u"Foo\nbar\nbla\n"], |
215 | 291 | [u"Homepage", u"http://samba.org/~jelmer/dulwich"], | ||
216 | 292 | ], bpr.user_defined_fields) | 301 | ], bpr.user_defined_fields) |
217 | 302 | |||
218 | 303 | def test_homepage(self): | ||
219 | 304 | # storeInDatabase stores homepage field. | ||
220 | 305 | uploadfile = self.createDebBinaryUploadFile( | ||
221 | 306 | "foo_0.42_i386.deb", "main/python", "unknown", "mypkg", "0.42", | ||
222 | 307 | None) | ||
223 | 308 | control = self.getBaseControl() | ||
224 | 309 | control["Python-Version"] = "2.5" | ||
225 | 310 | uploadfile.parseControl(control) | ||
226 | 311 | build = self.factory.makeBinaryPackageBuild() | ||
227 | 312 | bpr = uploadfile.storeInDatabase(build) | ||
228 | 313 | self.assertEquals( | ||
229 | 314 | u"http://samba.org/~jelmer/dulwich", bpr.homepage) | ||
230 | 293 | 315 | ||
231 | === modified file 'lib/lp/registry/browser/product.py' | |||
232 | --- lib/lp/registry/browser/product.py 2010-08-25 23:43:13 +0000 | |||
233 | +++ lib/lp/registry/browser/product.py 2010-09-02 11:38:52 +0000 | |||
234 | @@ -2001,7 +2001,7 @@ | |||
235 | 2001 | """Step 2 (of 2) in the +new project add wizard.""" | 2001 | """Step 2 (of 2) in the +new project add wizard.""" |
236 | 2002 | 2002 | ||
237 | 2003 | _field_names = ['displayname', 'name', 'title', 'summary', | 2003 | _field_names = ['displayname', 'name', 'title', 'summary', |
239 | 2004 | 'description', 'licenses', 'license_info', | 2004 | 'description', 'homepageurl', 'licenses', 'license_info', |
240 | 2005 | ] | 2005 | ] |
241 | 2006 | schema = IProduct | 2006 | schema = IProduct |
242 | 2007 | step_name = 'projectaddstep2' | 2007 | step_name = 'projectaddstep2' |
243 | @@ -2012,6 +2012,7 @@ | |||
244 | 2012 | 2012 | ||
245 | 2013 | custom_widget('displayname', TextWidget, displayWidth=50, label='Name') | 2013 | custom_widget('displayname', TextWidget, displayWidth=50, label='Name') |
246 | 2014 | custom_widget('name', ProductNameWidget, label='URL') | 2014 | custom_widget('name', ProductNameWidget, label='URL') |
247 | 2015 | custom_widget('homepageurl', TextWidget, displayWidth=30) | ||
248 | 2015 | custom_widget('licenses', LicenseWidget) | 2016 | custom_widget('licenses', LicenseWidget) |
249 | 2016 | custom_widget('license_info', GhostWidget) | 2017 | custom_widget('license_info', GhostWidget) |
250 | 2017 | 2018 | ||
251 | @@ -2169,6 +2170,7 @@ | |||
252 | 2169 | title=data['title'], | 2170 | title=data['title'], |
253 | 2170 | summary=data['summary'], | 2171 | summary=data['summary'], |
254 | 2171 | description=description, | 2172 | description=description, |
255 | 2173 | homepageurl=data.get('homepageurl'), | ||
256 | 2172 | licenses=data['licenses'], | 2174 | licenses=data['licenses'], |
257 | 2173 | license_info=data['license_info'], | 2175 | license_info=data['license_info'], |
258 | 2174 | project=project) | 2176 | project=project) |
259 | 2175 | 2177 | ||
260 | === modified file 'lib/lp/registry/stories/product/xx-product-add.txt' | |||
261 | --- lib/lp/registry/stories/product/xx-product-add.txt 2010-06-18 18:07:11 +0000 | |||
262 | +++ lib/lp/registry/stories/product/xx-product-add.txt 2010-09-02 11:38:52 +0000 | |||
263 | @@ -95,9 +95,9 @@ | |||
264 | 95 | The second step of the registration process does not allow Sample Person to | 95 | The second step of the registration process does not allow Sample Person to |
265 | 96 | modify the project's URL. | 96 | modify the project's URL. |
266 | 97 | 97 | ||
268 | 98 | >>> user_browser.getControl('URL') | 98 | >>> user_browser.getControl(name='field.name') |
269 | 99 | <Control name='field.name' type='hidden'> | 99 | <Control name='field.name' type='hidden'> |
271 | 100 | >>> print user_browser.getControl('URL').value | 100 | >>> print user_browser.getControl(name='field.name').value |
272 | 101 | aardvark | 101 | aardvark |
273 | 102 | 102 | ||
274 | 103 | Sample Person is given the opportunity though to change the title and | 103 | Sample Person is given the opportunity though to change the title and |
275 | 104 | 104 | ||
276 | === modified file 'lib/lp/soyuz/tests/test_publishing.py' | |||
277 | --- lib/lp/soyuz/tests/test_publishing.py 2010-08-30 15:00:23 +0000 | |||
278 | +++ lib/lp/soyuz/tests/test_publishing.py 2010-09-02 11:38:52 +0000 | |||
279 | @@ -357,7 +357,7 @@ | |||
280 | 357 | depends=None, recommends=None, suggests=None, conflicts=None, | 357 | depends=None, recommends=None, suggests=None, conflicts=None, |
281 | 358 | replaces=None, provides=None, pre_depends=None, enhances=None, | 358 | replaces=None, provides=None, pre_depends=None, enhances=None, |
282 | 359 | breaks=None, format=BinaryPackageFormat.DEB, debug_package=None, | 359 | breaks=None, format=BinaryPackageFormat.DEB, debug_package=None, |
284 | 360 | user_defined_fields=None): | 360 | user_defined_fields=None, homepage=None): |
285 | 361 | """Return the corresponding `BinaryPackageRelease`.""" | 361 | """Return the corresponding `BinaryPackageRelease`.""" |
286 | 362 | sourcepackagerelease = build.source_package_release | 362 | sourcepackagerelease = build.source_package_release |
287 | 363 | distroarchseries = build.distro_arch_series | 363 | distroarchseries = build.distro_arch_series |
288 | @@ -390,7 +390,7 @@ | |||
289 | 390 | binpackageformat=format, | 390 | binpackageformat=format, |
290 | 391 | priority=PackagePublishingPriority.STANDARD, | 391 | priority=PackagePublishingPriority.STANDARD, |
291 | 392 | debug_package=debug_package, | 392 | debug_package=debug_package, |
293 | 393 | user_defined_fields=user_defined_fields) | 393 | user_defined_fields=user_defined_fields, homepage=homepage) |
294 | 394 | 394 | ||
295 | 395 | # Create the corresponding binary file. | 395 | # Create the corresponding binary file. |
296 | 396 | if architecturespecific: | 396 | if architecturespecific: |
Looks good. Just two notes:
In line 49 of the diff: don't bother documenting tests as "Test that…" That's obvious from them being test methods. Just takes up space.
In line 94 of the diff: badly formatted line break. Try putting both arguments to the assertEqual together on the line after the invocation.
Please mention the "make lint" output in your merge proposals so that your reviewer doesn't need to worry about whether they have to play human linter. If there's too much pre-existing lint, well, throw in some drive-by cleanups!