Merge lp:~henninge/launchpad/bug-545354-enable-sharing into lp:~launchpad/launchpad/recife

Proposed by Henning Eggers
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 9139
Proposed branch: lp:~henninge/launchpad/bug-545354-enable-sharing
Merge into: lp:~launchpad/launchpad/recife
Diff against target: 918 lines (+666/-82)
5 files modified
database/schema/security.cfg (+1/-0)
lib/lp/translations/doc/potemplate.txt (+7/-3)
lib/lp/translations/interfaces/potemplate.py (+23/-0)
lib/lp/translations/model/potemplate.py (+161/-65)
lib/lp/translations/tests/test_shared_potemplate.py (+474/-14)
To merge this branch: bzr merge lp:~henninge/launchpad/bug-545354-enable-sharing
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) Approve
Review via email: mp+27123@code.launchpad.net

Commit message

Enable message sharing between products and source packages.

Description of the change

= Bug 545354 =

Enlarges the collection of sharing templates in a POTemplateSharingSubset to include templates from linked products and source packages. Actually, in some cases it does not only "enlarge" but also "shift". The old implementation had all source packages of the same name (sourcepackagename) within a distribution share templates, this new implementation makes that connection through linkage to an upstream product. Consequently, all source packages that are linked to any series of the same product will share templates - independently of the name of the source package. Usually though, the sourcepackagename will be identical. By this we will loose packages in older Ubuntu series because the Packaging table (that holds the upstream link information) used to be insufficiently populated so that not each series was properly linked. Since Maverick this has been fixed so that future series will work just fine with this.

== Pre-implementation discussion ==

We discussed some of this in Recife in March but also just recently. The latest findings can be found in my comments on the bug. One design goal was to make sure the number of SQL queries remains constant and independent of the number of templates in the collection.

== Implementation details ==

The query in _queryByProduct is best understood by looking at this little picture: http://paste.ubuntu.com/447067/ Since a POTemplate can be linked to *either* a ProductSeries *or* a SourcePackage (distroseries, sourcepackagename), these need to be left-joined.
Also in this method I used ID throughout although not all relations need to be that explicit but I found mixing those more confusing to read.

== Tests ==

A new test case was created to test getSharingPOTemplates and getOrCreateSharedPOTMsgSet. The latter is only tested briefly because it uses the first and there is no need to re-create all test conditions. The tests also make sure the number of sql statements remains constant (at 1).

./test -vvt MessageSharingProductPackage

== Lint ==

No lint left.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :

Seeing how many tests were affected, I'd suggest the following test command which tests all translation tests:

bin/test -vvt lp.translations

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (26.3 KiB)

Hi Henning,

This looks good. I just have a few comments below.

merge-conditional

-Edwin

>=== modified file 'lib/lp/translations/model/potemplate.py'
>--- lib/lp/translations/model/potemplate.py 2010-01-12 21:29:03 +0000
>+++ lib/lp/translations/model/potemplate.py 2010-06-16 19:25:05 +0000
>@@ -184,6 +184,21 @@
>
> _uses_english_msgids = None
>
>+ @cachedproperty
>+ def _sharing_ids(self):
>+ """Return the IDs of all sharing templates including this one."""
>+ subset = getUtility(IPOTemplateSet).getSharingSubset(
>+ product=self.product,
>+ distribution=self.distribution,
>+ sourcepackagename=self.sourcepackagename)
>+ # Convert to a list for caching.
>+ result = list(subset.getSharingPOTemplateIDs(self.name))
>+ if len(result) == 0:
>+ # Always return at least the template itself.
>+ return [self.id]
>+ else:
>+ return result

Should you also make sure that self.id is in result even if len != 0?

> def __storm_invalidated__(self):
> self.clearPOFileCache()
> self._uses_english_msgids = None
>@@ -1387,45 +1425,102 @@
> package = template.sourcepackagename.name
> return (template.name, package)
>
>- def _iterate_potemplates(self, name_pattern=None):
>- """Yield all templates matching the provided argument.
>-
>- This is much like a `IPOTemplateSubset`, except it operates on
>- `Product`s and `Distribution`s rather than `ProductSeries` and
>- `DistroSeries`.
>- """
>- if self.product:
>- subsets = [
>- self.potemplateset.getSubset(productseries=series)
>- for series in self.product.series
>- ]
>+ def _queryByProduct(self, templatename_clause):
>+ """Build the query that finds POTemplates by their linked product.
>+
>+ Queries the Packaging table to find templates in linked source
>+ packages, too.
>+
>+ :param templatename_clause: A string or a storm expression to
>+ add to the where clause of the query that will select the template
>+ name.
>+ :return: A ResultSet for the query.
>+ """
>+ from lp.registry.model.productseries import ProductSeries
>+
>+ ProductSeries1 = ClassAlias(ProductSeries)
>+ origin = LeftJoin(
>+ LeftJoin(
>+ POTemplate, ProductSeries,
>+ POTemplate.productseriesID == ProductSeries.id),
>+ Join(
>+ Packaging, ProductSeries1,
>+ Packaging.productseriesID == ProductSeries1.id),
>+ And(
>+ Packaging.distroseriesID == POTemplate.distroseriesID,
>+ Packaging.sourcepackagenameID == (
>+ POTemplate.sourcepackagenameID)
>+ )
>+ )
>+ return Store.of(self.product).using(origin).find(
>+ POTemplate,
>+ And(
>+ Or(
>+ ProductSeries.productID == self.product.id,
>+ ProductSeries1.productID == self.product.id
>+ ),
>+ ...

review: Approve
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (3.2 KiB)

Am 16.06.2010 23:24, schrieb Edwin Grubbs:
> This looks good. I just have a few comments below.

Thanks for the review. I explained one away and fixed two. ;)

>> === modified file 'lib/lp/translations/model/potemplate.py'
>> --- lib/lp/translations/model/potemplate.py 2010-01-12 21:29:03 +0000
>> +++ lib/lp/translations/model/potemplate.py 2010-06-16 19:25:05 +0000
>> @@ -184,6 +184,21 @@
>>
>> _uses_english_msgids = None
>>
>> + @cachedproperty
>> + def _sharing_ids(self):
>> + """Return the IDs of all sharing templates including this one."""
>> + subset = getUtility(IPOTemplateSet).getSharingSubset(
>> + product=self.product,
>> + distribution=self.distribution,
>> + sourcepackagename=self.sourcepackagename)
>> + # Convert to a list for caching.
>> + result = list(subset.getSharingPOTemplateIDs(self.name))
>> + if len(result) == 0:
>> + # Always return at least the template itself.
>> + return [self.id]
>> + else:
>> + return result
>
>
> Should you also make sure that self.id is in result even if len != 0?

No. Actually, the len == 0 case is basically a "no sharing possible case" and
with a single id in the list no sharing happens. If we get any ids back, those
are the sharing ones and we can trust them to include all we need.

[...]

>> def groupEquivalentPOTemplates(self, name_pattern=None): """See IPOTemplateSharingSubset.""" equivalents = {}
>>
>> - for template in self._iterate_potemplates(name_pattern):
>> + if name_pattern is None:
>> + templatename_clause = "1=1"
>> + else:
>> + templatename_clause = "potemplate.name ~ '%s'" % name_pattern
>
>
> I think you should either escape the name_pattern with sqlvalues,
> or you should convert it to a storm conditional.

Yes, thank you. I fixed that by using sqlvalues. Also I replaced "1=1" with
True since we are still in storm land here. I also pulled this out into
another variant of getSharingPOTemplates and added tests for it.

>
>
>> +
>> + for template in self._queryPOTemplates(templatename_clause):
>> key = self._get_potemplate_equivalence_class(template)
>> if key not in equivalents:
>> equivalents[key] = []
>> === modified file 'lib/lp/translations/tests/test_shared_potemplate.py'
>> --- lib/lp/translations/tests/test_shared_potemplate.py 2010-05-18 14:00:10 +0000
>> +++ lib/lp/translations/tests/test_shared_potemplate.py 2010-06-16 19:25:05 +0000
>> @@ -177,5 +176,398 @@
>> self.assertEquals(potmsgset, shared_potmsgset)
>>
>>
>> +class TestMessageSharingProductPackage(TestCaseWithFactory):
>> + """Test message sharing between a product and a package.
>> +
>> + Each test uses assertStatementCount to make sure the number of SQL
>> + queries does not change. This was integrated here to avoid having
>> + a second test case just for statement counts.
>> + The current implementation is good and only needs one statement."""
>
>
> Ending triple-quotes belongs by itself on the following line, since
> this is a multiline docstring.
>

True, thanks.

...

Read more...

=== modified file 'lib/lp/translations/interfaces/potemplate.py'
--- lib/lp/translations/interfaces/potemplate.py 2010-06-09 07:36:35 +0000
+++ lib/lp/translations/interfaces/potemplate.py 2010-06-18 10:16:50 +0000
@@ -695,6 +695,17 @@
695 :return: A list of all potemplates of the same name from all series.695 :return: A list of all potemplates of the same name from all series.
696 """696 """
697697
698 def getSharingPOTemplatesRegex(name_pattern=None):
699 """Find all sharing templates with names matching the given pattern.
700
701 If name_pattern is None, match is performed on the template name.
702 Use with care as it may return all templates in a distribution!
703
704 :param name_pattern: A POSIX regular expression that the template
705 is matched against.
706 :return: A list of all potemplates matching the pattern.
707 """
708
698 def getSharingPOTemplateIDs(potemplate_name):709 def getSharingPOTemplateIDs(potemplate_name):
699 """Find database ids of all sharing templates of the given name.710 """Find database ids of all sharing templates of the given name.
700711
701712
=== modified file 'lib/lp/translations/model/potemplate.py'
--- lib/lp/translations/model/potemplate.py 2010-06-16 16:31:06 +0000
+++ lib/lp/translations/model/potemplate.py 2010-06-18 11:05:58 +0000
@@ -1506,6 +1506,16 @@
1506 return self._queryPOTemplates(1506 return self._queryPOTemplates(
1507 POTemplate.name == potemplate_name)1507 POTemplate.name == potemplate_name)
15081508
1509 def getSharingPOTemplatesRegex(self, name_pattern=None):
1510 """See IPOTemplateSharingSubset."""
1511 if name_pattern is None:
1512 templatename_clause = True
1513 else:
1514 templatename_clause = (
1515 "potemplate.name ~ %s" % sqlvalues(name_pattern))
1516
1517 return self._queryPOTemplates(templatename_clause)
1518
1509 def getSharingPOTemplateIDs(self, potemplate_name):1519 def getSharingPOTemplateIDs(self, potemplate_name):
1510 """See IPOTemplateSharingSubset."""1520 """See IPOTemplateSharingSubset."""
1511 return self.getSharingPOTemplates(potemplate_name).values(1521 return self.getSharingPOTemplates(potemplate_name).values(
@@ -1515,12 +1525,7 @@
1515 """See IPOTemplateSharingSubset."""1525 """See IPOTemplateSharingSubset."""
1516 equivalents = {}1526 equivalents = {}
15171527
1518 if name_pattern is None:1528 for template in self.getSharingPOTemplatesRegex(name_pattern):
1519 templatename_clause = "1=1"
1520 else:
1521 templatename_clause = "potemplate.name ~ '%s'" % name_pattern
1522
1523 for template in self._queryPOTemplates(templatename_clause):
1524 key = self._get_potemplate_equivalence_class(template)1529 key = self._get_potemplate_equivalence_class(template)
1525 if key not in equivalents:1530 if key not in equivalents:
1526 equivalents[key] = []1531 equivalents[key] = []
15271532
=== modified file 'lib/lp/translations/tests/test_shared_potemplate.py'
--- lib/lp/translations/tests/test_shared_potemplate.py 2010-06-09 09:16:54 +0000
+++ lib/lp/translations/tests/test_shared_potemplate.py 2010-06-18 11:15:54 +0000
@@ -7,6 +7,7 @@
77
8import unittest8import unittest
99
10from storm.exceptions import DataError
10from zope.component import getUtility11from zope.component import getUtility
11from zope.security.proxy import removeSecurityProxy12from zope.security.proxy import removeSecurityProxy
1213
@@ -176,13 +177,69 @@
176 self.assertEquals(potmsgset, shared_potmsgset)177 self.assertEquals(potmsgset, shared_potmsgset)
177178
178179
180class TestSharingPOTemplatesRegex(TestCaseWithFactory):
181 """Isolate tests for regular expression use in SharingSubset."""
182
183 layer = ZopelessDatabaseLayer
184
185 def setUp(self):
186 super(TestSharingPOTemplatesRegex, self).setUp()
187 self.product = self.factory.makeProduct()
188 self.product.official_rosetta = True
189 self.trunk = self.product.getSeries('trunk')
190 self.potemplateset = getUtility(IPOTemplateSet)
191
192 def _makeTemplates(self, names):
193 # Create some templates with the given names.
194 return [
195 self.factory.makePOTemplate(productseries=self.trunk, name=name)
196 for name in names]
197
198 def test_getSharingPOTemplatesRegex_baseline(self):
199 # Baseline test.
200 templates = self._makeTemplates(['foo', 'foo-bar', 'foo-two'])
201 subset = self.potemplateset.getSharingSubset(product=self.product)
202 self.assertContentEqual(
203 templates, subset.getSharingPOTemplatesRegex('foo.*'))
204
205 def test_getSharingPOTemplatesRegex_not_all(self):
206 # A template may not match.
207 templates = self._makeTemplates(['foo', 'foo-bar', 'foo-two'])
208 subset = self.potemplateset.getSharingSubset(product=self.product)
209 self.assertContentEqual(
210 templates[1:], subset.getSharingPOTemplatesRegex('foo-.*'))
211
212 def test_getSharingPOTemplatesRegex_all(self):
213 # Not passing a pattern returns all templates.
214 templates = self._makeTemplates(['foo', 'foo-bar', 'foo-two'])
215 subset = self.potemplateset.getSharingSubset(product=self.product)
216 self.assertContentEqual(
217 templates, subset.getSharingPOTemplatesRegex())
218
219 def test_getSharingPOTemplatesRegex_robustness_quotes(self):
220 # Quotes in the pattern can be dangerous.
221 subset = self.potemplateset.getSharingSubset(product=self.product)
222 self.assertContentEqual(
223 [], subset.getSharingPOTemplatesRegex("'\""))
224
225 def test_getSharingPOTemplatesRegex_robustness_backslash(self):
226 # A backslash at the end could escape enclosing quotes without
227 # proper escaping, leading to a SyntaxError or even a successful
228 # exploit. Instead, storm should complain about an invalid expression
229 # by raising DataError.
230 subset = self.potemplateset.getSharingSubset(product=self.product)
231 self.assertRaises(
232 DataError, list, subset.getSharingPOTemplatesRegex("foo.*\\"))
233
234
179class TestMessageSharingProductPackage(TestCaseWithFactory):235class TestMessageSharingProductPackage(TestCaseWithFactory):
180 """Test message sharing between a product and a package.236 """Test message sharing between a product and a package.
181237
182 Each test uses assertStatementCount to make sure the number of SQL238 Each test uses assertStatementCount to make sure the number of SQL
183 queries does not change. This was integrated here to avoid having239 queries does not change. This was integrated here to avoid having
184 a second test case just for statement counts.240 a second test case just for statement counts.
185 The current implementation is good and only needs one statement."""241 The current implementation is good and only needs one statement.
242 """
186243
187 layer = ZopelessDatabaseLayer244 layer = ZopelessDatabaseLayer
188245
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I looked over the incremental diff at Henning's request. A few notes, mostly already discussed on IRC. I think having the separate unit test for the regex matching (which was a bit of a wildcard, ha ha) worked out really well.

These came up on IRC:
 * Backquote identifiers in docstrings: "See `IPOTemplateSharingSubset`."""
 * getSharingPOTemplatesRegex says you're getting a regex, not templates; add "By."
 * No need to create a Product and ProductSeries; create a series and use its product.

The unit tests mostly follow the pattern "create templates with names [...]; create a subset; select from subset by regex; verify output." Most of these would be one-liners if you had a method for "create templates with names [...] (parameter 1); create a subset; select from subset by regex (parameter 2); return list of names of the selected templates." The caller could then assertContentsEqual the result against a list of expected strings. That would also help with:
 * Making the output more obvious (the relevance of "templates[1:]" is less clear).
 * Testing lots of other things that could go wrong; see below.
 * Eliminating implicit state. You wouldn't need any setUp at all.

Other notes:
 * You may want to test that you're really getting a match, not a search: "xyz" should not match "y.*" As long as we're not taking the regex from user input but from existing template names, this is a bigger risk than unescaped quotes. (Not that I disagree with your approach of testing for those right from the start!)
 * "# Quotes in the pattern can be dangerous." To me that looks like you're specifying the appropriate behaviour! How about "quotes do not confuse the regex match"?
 * I'd test single and double quotes separately, not in the same string.
 * When you test for quoting robustness, you might as well set up a template to establish that you don't get any unexpected matches.
 * You don't exercise the case where no templates match.

Apart from those notes, the incremental diff has my unreserved blessing.

Jeroen

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-05-20 13:59:11 +0000
+++ database/schema/security.cfg 2010-06-22 07:46:30 +0000
@@ -1341,6 +1341,7 @@
1341public.distribution = SELECT1341public.distribution = SELECT
1342public.distroseries = SELECT1342public.distroseries = SELECT
1343public.language = SELECT1343public.language = SELECT
1344public.packaging = SELECT
1344public.person = SELECT1345public.person = SELECT
1345public.pofile = SELECT, INSERT, UPDATE1346public.pofile = SELECT, INSERT, UPDATE
1346public.pomsgid = SELECT1347public.pomsgid = SELECT
13471348
=== modified file 'lib/lp/translations/doc/potemplate.txt'
--- lib/lp/translations/doc/potemplate.txt 2010-04-26 16:00:31 +0000
+++ lib/lp/translations/doc/potemplate.txt 2010-06-22 07:46:30 +0000
@@ -228,7 +228,7 @@
228228
229== getPOFileByPath ==229== getPOFileByPath ==
230230
231We can get an IPOFile inside a templage based on its path.231We can get an IPOFile inside a template based on its path.
232232
233 >>> pofile = potemplate.getPOFileByPath('es.po')233 >>> pofile = potemplate.getPOFileByPath('es.po')
234 >>> print pofile.title234 >>> print pofile.title
@@ -614,7 +614,10 @@
614 >>> print exported_translation_file.content_type614 >>> print exported_translation_file.content_type
615 application/x-gtar615 application/x-gtar
616616
617Inspecting the tarball content, we have the list of entries exported.617Inspecting the tarball content, we have the list of entries exported. This
618includes the 'pt' POFile that was created earlier on the 'evolution'
619product as this is sharing translations with the source package that this
620potemplate is from.
618621
619 >>> from canonical.launchpad.helpers import string_to_tarfile622 >>> from canonical.launchpad.helpers import string_to_tarfile
620 >>> tarfile_string = exported_translation_file.read()623 >>> tarfile_string = exported_translation_file.read()
@@ -625,7 +628,7 @@
625 >>> sorted(name.rstrip('/') for name in tarfile.getnames())628 >>> sorted(name.rstrip('/') for name in tarfile.getnames())
626 ['evolution-2.2', 'evolution-2.2/evolution-2.2-es.po',629 ['evolution-2.2', 'evolution-2.2/evolution-2.2-es.po',
627 'evolution-2.2/evolution-2.2-ja.po', 'evolution-2.2/evolution-2.2-xh.po',630 'evolution-2.2/evolution-2.2-ja.po', 'evolution-2.2/evolution-2.2-xh.po',
628 'po', 'po/evolution-2.2.pot']631 'po', 'po/evolution-2.2-pt.po', 'po/evolution-2.2.pot']
629632
630The *-es.po file is indeed the Spanish translation...633The *-es.po file is indeed the Spanish translation...
631634
@@ -644,6 +647,7 @@
644 evolution-2.2/evolution-2.2-ja.po647 evolution-2.2/evolution-2.2-ja.po
645 evolution-2.2/evolution-2.2-xh.po648 evolution-2.2/evolution-2.2-xh.po
646 po/649 po/
650 po/evolution-2.2-pt.po
647 po/evolution-2.2.pot651 po/evolution-2.2.pot
648 >>> pofile = simple_popen2(652 >>> pofile = simple_popen2(
649 ... ['tar', 'zxfO', '-', 'evolution-2.2/evolution-2.2-es.po'],653 ... ['tar', 'zxfO', '-', 'evolution-2.2/evolution-2.2-es.po'],
650654
=== modified file 'lib/lp/translations/interfaces/potemplate.py'
--- lib/lp/translations/interfaces/potemplate.py 2009-11-27 12:50:16 +0000
+++ lib/lp/translations/interfaces/potemplate.py 2010-06-22 07:46:30 +0000
@@ -695,6 +695,29 @@
695 :return: A list of all potemplates of the same name from all series.695 :return: A list of all potemplates of the same name from all series.
696 """696 """
697697
698 def getSharingPOTemplatesByRegex(name_pattern=None):
699 """Find all sharing templates with names matching the given pattern.
700
701 If name_pattern is None, match is performed on the template name.
702 Use with care as it may return all templates in a distribution!
703
704 :param name_pattern: A POSIX regular expression that the template
705 is matched against.
706 :return: A list of all potemplates matching the pattern.
707 """
708
709 def getSharingPOTemplateIDs(potemplate_name):
710 """Find database ids of all sharing templates of the given name.
711
712 For distributions this method requires that sourcepackagename is set.
713 This avoids serialization of full POTemplate objects.
714
715 :param potemplate_name: The name of the template for which to find
716 sharing equivalents.
717 :return: A list of database ids of all potemplates of the same name
718 from all series.
719 """
720
698 def groupEquivalentPOTemplates(name_pattern=None):721 def groupEquivalentPOTemplates(name_pattern=None):
699 """Within given IProduct or IDistribution, find equivalent templates.722 """Within given IProduct or IDistribution, find equivalent templates.
700723
701724
=== modified file 'lib/lp/translations/model/potemplate.py'
--- lib/lp/translations/model/potemplate.py 2010-01-12 21:29:03 +0000
+++ lib/lp/translations/model/potemplate.py 2010-06-22 07:46:30 +0000
@@ -18,7 +18,6 @@
18import datetime18import datetime
19import logging19import logging
20import os20import os
21import re
2221
23from psycopg2.extensions import TransactionRollbackError22from psycopg2.extensions import TransactionRollbackError
24from sqlobject import (23from sqlobject import (
@@ -26,7 +25,7 @@
26 StringCol)25 StringCol)
27from storm.expr import Alias, And, Desc, In, Join, LeftJoin, Or, SQL26from storm.expr import Alias, And, Desc, In, Join, LeftJoin, Or, SQL
28from storm.info import ClassAlias27from storm.info import ClassAlias
29from storm.store import Store28from storm.store import EmptyResultSet, Store
30from zope.component import getAdapter, getUtility29from zope.component import getAdapter, getUtility
31from zope.interface import implements30from zope.interface import implements
32from zope.security.proxy import removeSecurityProxy31from zope.security.proxy import removeSecurityProxy
@@ -43,6 +42,7 @@
43from lp.services.database.prejoin import prejoin42from lp.services.database.prejoin import prejoin
44from lp.services.worlddata.model.language import Language43from lp.services.worlddata.model.language import Language
45from lp.registry.interfaces.person import validate_public_person44from lp.registry.interfaces.person import validate_public_person
45from lp.registry.model.packaging import Packaging
46from lp.registry.model.sourcepackagename import SourcePackageName46from lp.registry.model.sourcepackagename import SourcePackageName
47from lp.translations.model.pofile import POFile, DummyPOFile47from lp.translations.model.pofile import POFile, DummyPOFile
48from lp.translations.model.pomsgid import POMsgID48from lp.translations.model.pomsgid import POMsgID
@@ -184,6 +184,21 @@
184184
185 _uses_english_msgids = None185 _uses_english_msgids = None
186186
187 @cachedproperty
188 def _sharing_ids(self):
189 """Return the IDs of all sharing templates including this one."""
190 subset = getUtility(IPOTemplateSet).getSharingSubset(
191 product=self.product,
192 distribution=self.distribution,
193 sourcepackagename=self.sourcepackagename)
194 # Convert to a list for caching.
195 result = list(subset.getSharingPOTemplateIDs(self.name))
196 if len(result) == 0:
197 # Always return at least the template itself.
198 return [self.id]
199 else:
200 return result
201
187 def __storm_invalidated__(self):202 def __storm_invalidated__(self):
188 self.clearPOFileCache()203 self.clearPOFileCache()
189 self._uses_english_msgids = None204 self._uses_english_msgids = None
@@ -339,8 +354,7 @@
339 if self.productseries is not None:354 if self.productseries is not None:
340 return self.productseries355 return self.productseries
341 elif self.distroseries is not None:356 elif self.distroseries is not None:
342 from lp.registry.model.sourcepackage import \357 from lp.registry.model.sourcepackage import SourcePackage
343 SourcePackage
344 return SourcePackage(distroseries=self.distroseries,358 return SourcePackage(distroseries=self.distroseries,
345 sourcepackagename=self.sourcepackagename)359 sourcepackagename=self.sourcepackagename)
346 raise AssertionError('Unknown POTemplate translation target')360 raise AssertionError('Unknown POTemplate translation target')
@@ -560,31 +574,10 @@
560 ]574 ]
561 clause_tables = ['TranslationTemplateItem']575 clause_tables = ['TranslationTemplateItem']
562 if sharing_templates:576 if sharing_templates:
563 clauses.extend([577 clauses.append(
564 'TranslationTemplateItem.potemplate = POTemplate.id',578 'TranslationTemplateItem.potemplate in %s' % sqlvalues(
565 'TranslationTemplateItem.potmsgset = POTMsgSet.id',579 self._sharing_ids)
566 'POTemplate.name = %s' % sqlvalues(self.name),580 )
567 ])
568 product = None
569 distribution = None
570 clause_tables.append('POTemplate')
571 if self.productseries is not None:
572 product = self.productseries.product
573 clauses.extend([
574 'POTemplate.productseries=ProductSeries.id',
575 'ProductSeries.product %s' % self._null_quote(product)
576 ])
577 clause_tables.append('ProductSeries')
578 elif self.distroseries is not None:
579 distribution = self.distroseries.distribution
580 clauses.extend([
581 'POTemplate.distroseries=DistroSeries.id',
582 'DistroSeries.distribution %s' % (
583 self._null_quote(distribution)),
584 'POTemplate.sourcepackagename %s' % self._null_quote(
585 self.sourcepackagename),
586 ])
587 clause_tables.append('DistroSeries')
588 else:581 else:
589 clauses.append(582 clauses.append(
590 'TranslationTemplateItem.potemplate = %s' % sqlvalues(self))583 'TranslationTemplateItem.potemplate = %s' % sqlvalues(self))
@@ -1374,9 +1367,54 @@
1374 assert distribution or not sourcepackagename, (1367 assert distribution or not sourcepackagename, (
1375 "Picking a source package only makes sense with a distribution.")1368 "Picking a source package only makes sense with a distribution.")
1376 self.potemplateset = potemplateset1369 self.potemplateset = potemplateset
1377 self.distribution = distribution1370 self.share_by_name = False
1378 self.sourcepackagename = sourcepackagename1371 self.subset = None
1379 self.product = product1372
1373 if distribution is not None:
1374 self.distribution = distribution
1375 self.sourcepackagename = sourcepackagename
1376 if sourcepackagename is not None:
1377 product = self._findUpstreamProduct(
1378 distribution, sourcepackagename)
1379 if product is None:
1380 self.share_by_name = self._canShareByName(
1381 distribution, sourcepackagename)
1382 if product is not None:
1383 self.product = product
1384
1385 def _findUpstreamProduct(self, distribution, sourcepackagename):
1386 """Find the upstream product by looking at the translation focus.
1387
1388 The translation focus is used to pick a distroseries, so a source
1389 package instance can be created. If no translation focus is set,
1390 the distribution's current series is used."""
1391
1392 from lp.registry.model.sourcepackage import SourcePackage
1393 distroseries = distribution.translation_focus
1394 if distroseries is None:
1395 distroseries = distribution.currentseries
1396 sourcepackage = SourcePackage(sourcepackagename, distroseries)
1397 if sourcepackage.productseries is None:
1398 return None
1399 return sourcepackage.productseries.product
1400
1401 def _canShareByName(self, distribution, sourcepackagename):
1402 """Determine if sharing by sourcepackagename is a wise thing.
1403
1404 Without a product, the linkage between sharing packages can only be
1405 determined by their name. This is only (fairly) safe if none of these
1406 is packaged elsewhere.
1407 """
1408 from lp.registry.model.distroseries import DistroSeries
1409 origin = Join(
1410 Packaging, DistroSeries,
1411 Packaging.distroseries == DistroSeries.id)
1412 result = Store.of(distribution).using(origin).find(
1413 Packaging,
1414 And(DistroSeries.distribution == distribution.id,
1415 Packaging.sourcepackagename == sourcepackagename.id)
1416 ).count()
1417 return result == 0
13801418
13811419
1382 def _get_potemplate_equivalence_class(self, template):1420 def _get_potemplate_equivalence_class(self, template):
@@ -1387,45 +1425,107 @@
1387 package = template.sourcepackagename.name1425 package = template.sourcepackagename.name
1388 return (template.name, package)1426 return (template.name, package)
13891427
1390 def _iterate_potemplates(self, name_pattern=None):1428 def _queryByProduct(self, templatename_clause):
1391 """Yield all templates matching the provided argument.1429 """Build the query that finds POTemplates by their linked product.
13921430
1393 This is much like a `IPOTemplateSubset`, except it operates on1431 Queries the Packaging table to find templates in linked source
1394 `Product`s and `Distribution`s rather than `ProductSeries` and1432 packages, too.
1395 `DistroSeries`.1433
1396 """1434 :param templatename_clause: A string or a storm expression to
1397 if self.product:1435 add to the where clause of the query that will select the template
1398 subsets = [1436 name.
1399 self.potemplateset.getSubset(productseries=series)1437 :return: A ResultSet for the query.
1400 for series in self.product.series1438 """
1401 ]1439 from lp.registry.model.productseries import ProductSeries
1440
1441 ProductSeries1 = ClassAlias(ProductSeries)
1442 origin = LeftJoin(
1443 LeftJoin(
1444 POTemplate, ProductSeries,
1445 POTemplate.productseriesID == ProductSeries.id),
1446 Join(
1447 Packaging, ProductSeries1,
1448 Packaging.productseriesID == ProductSeries1.id),
1449 And(
1450 Packaging.distroseriesID == POTemplate.distroseriesID,
1451 Packaging.sourcepackagenameID == (
1452 POTemplate.sourcepackagenameID)
1453 )
1454 )
1455 return Store.of(self.product).using(origin).find(
1456 POTemplate,
1457 And(
1458 Or(
1459 ProductSeries.productID == self.product.id,
1460 ProductSeries1.productID == self.product.id
1461 ),
1462 templatename_clause
1463 ))
1464
1465 def _queryBySourcepackagename(self, templatename_clause):
1466 """Build the query that finds POTemplates by their names.
1467
1468 :param templatename_clause: A string or a storm expression to
1469 add to the where clause of the query that will select the template
1470 name.
1471 :return: A ResultSet for the query.
1472 """
1473 from lp.registry.model.distroseries import DistroSeries
1474 origin = Join(
1475 POTemplate, DistroSeries,
1476 POTemplate.distroseries == DistroSeries.id)
1477 return Store.of(self.distribution).using(origin).find(
1478 POTemplate,
1479 And(
1480 DistroSeries.distributionID == self.distribution.id,
1481 POTemplate.sourcepackagename == self.sourcepackagename,
1482 templatename_clause
1483 ))
1484
1485 def _queryByDistribution(self, templatename_clause):
1486 """Special case when templates are searched across a distribution."""
1487 return Store.of(self.distribution).find(
1488 POTemplate, templatename_clause)
1489
1490 def _queryPOTemplates(self, templatename_clause):
1491 """Select the right query to be used."""
1492 if self.product is not None:
1493 return self._queryByProduct(templatename_clause)
1494 elif self.share_by_name:
1495 return self._queryBySourcepackagename(templatename_clause)
1496 elif self.distribution is not None and self.sourcepackagename is None:
1497 return self._queryByDistribution(templatename_clause)
1402 else:1498 else:
1403 subsets = [1499 return EmptyResultSet()
1404 self.potemplateset.getSubset(
1405 distroseries=series,
1406 sourcepackagename=self.sourcepackagename)
1407 for series in self.distribution.series
1408 ]
1409 for subset in subsets:
1410 for template in subset:
1411 if name_pattern is None or re.match(name_pattern,
1412 template.name):
1413 yield template
14141500
1415 def getSharingPOTemplates(self, potemplate_name):1501 def getSharingPOTemplates(self, potemplate_name):
1416 """See IPOTemplateSharingSubset."""1502 """See `IPOTemplateSharingSubset`."""
1417 if self.distribution is not None:1503 if self.distribution is not None:
1418 assert self.sourcepackagename is not None, (1504 assert self.sourcepackagename is not None, (
1419 "Need sourcepackagename to select from distribution.")1505 "Need sourcepackagename to select from distribution.")
14201506 return self._queryPOTemplates(
1421 escaped_potemplate_name = re.escape(potemplate_name)1507 POTemplate.name == potemplate_name)
1422 return self._iterate_potemplates("^%s$" % escaped_potemplate_name)1508
1509 def getSharingPOTemplatesByRegex(self, name_pattern=None):
1510 """See `IPOTemplateSharingSubset`."""
1511 if name_pattern is None:
1512 templatename_clause = True
1513 else:
1514 templatename_clause = (
1515 "potemplate.name ~ %s" % sqlvalues(name_pattern))
1516
1517 return self._queryPOTemplates(templatename_clause)
1518
1519 def getSharingPOTemplateIDs(self, potemplate_name):
1520 """See `IPOTemplateSharingSubset`."""
1521 return self.getSharingPOTemplates(potemplate_name).values(
1522 POTemplate.id)
14231523
1424 def groupEquivalentPOTemplates(self, name_pattern=None):1524 def groupEquivalentPOTemplates(self, name_pattern=None):
1425 """See IPOTemplateSharingSubset."""1525 """See `IPOTemplateSharingSubset`."""
1426 equivalents = {}1526 equivalents = {}
14271527
1428 for template in self._iterate_potemplates(name_pattern):1528 for template in self.getSharingPOTemplatesByRegex(name_pattern):
1429 key = self._get_potemplate_equivalence_class(template)1529 key = self._get_potemplate_equivalence_class(template)
1430 if key not in equivalents:1530 if key not in equivalents:
1431 equivalents[key] = []1531 equivalents[key] = []
@@ -1510,10 +1610,6 @@
1510 if flag1610 if flag
1511 ])1611 ])
15121612
1513 # Store sequences so we can detect later whether we changed the
1514 # message.
1515 sequence = row.sequence
1516
1517 # Store the message.1613 # Store the message.
1518 messages.append(msgset)1614 messages.append(msgset)
15191615
15201616
=== modified file 'lib/lp/translations/tests/test_shared_potemplate.py'
--- lib/lp/translations/tests/test_shared_potemplate.py 2010-05-18 14:00:10 +0000
+++ lib/lp/translations/tests/test_shared_potemplate.py 2010-06-22 07:46:30 +0000
@@ -7,13 +7,16 @@
77
8import unittest8import unittest
99
10from storm.exceptions import DataError
11from zope.component import getUtility
10from zope.security.proxy import removeSecurityProxy12from zope.security.proxy import removeSecurityProxy
1113
12from lp.testing.factory import LaunchpadObjectFactory14from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
13from canonical.testing import ZopelessDatabaseLayer15from canonical.testing import ZopelessDatabaseLayer
1416from lp.testing import TestCaseWithFactory
1517from lp.translations.interfaces.potemplate import IPOTemplateSet
16class TestTranslationSharingPOTemplate(unittest.TestCase):18
19class TestTranslationSharingPOTemplate(TestCaseWithFactory):
17 """Test behaviour of "sharing" PO templates."""20 """Test behaviour of "sharing" PO templates."""
1821
19 layer = ZopelessDatabaseLayer22 layer = ZopelessDatabaseLayer
@@ -22,20 +25,19 @@
22 """Set up context to test in."""25 """Set up context to test in."""
23 # Create a product with two series and sharing POTemplates26 # Create a product with two series and sharing POTemplates
24 # in different series ('devel' and 'stable').27 # in different series ('devel' and 'stable').
25 factory = LaunchpadObjectFactory()28 super(TestTranslationSharingPOTemplate, self).setUp()
26 self.factory = factory29 self.foo = self.factory.makeProduct()
27 self.foo = factory.makeProduct()30 self.foo_devel = self.factory.makeProductSeries(
28 self.foo_devel = factory.makeProductSeries(
29 name='devel', product=self.foo)31 name='devel', product=self.foo)
30 self.foo_stable = factory.makeProductSeries(32 self.foo_stable = self.factory.makeProductSeries(
31 name='stable', product=self.foo)33 name='stable', product=self.foo)
32 self.foo.official_rosetta = True34 self.foo.official_rosetta = True
3335
34 # POTemplate is a 'sharing' one if it has the same name ('messages').36 # POTemplate is a 'sharing' one if it has the same name ('messages').
35 self.devel_potemplate = factory.makePOTemplate(37 self.devel_potemplate = self.factory.makePOTemplate(
36 productseries=self.foo_devel, name="messages")38 productseries=self.foo_devel, name="messages")
37 self.stable_potemplate = factory.makePOTemplate(self.foo_stable,39 self.stable_potemplate = self.factory.makePOTemplate(
38 name="messages")40 self.foo_stable, name="messages")
3941
40 # Create a single POTMsgSet that is used across all tests,42 # Create a single POTMsgSet that is used across all tests,
41 # and add it to only one of the POTemplates.43 # and add it to only one of the POTemplates.
@@ -106,8 +108,6 @@
106 self.assertEquals(has_message_id, True)108 self.assertEquals(has_message_id, True)
107109
108 def test_hasPluralMessage(self):110 def test_hasPluralMessage(self):
109 naked_potemplate = removeSecurityProxy(self.devel_potemplate)
110
111 # At the moment, a POTemplate has no plural form messages.111 # At the moment, a POTemplate has no plural form messages.
112 self.assertEquals(self.devel_potemplate.hasPluralMessage(), False)112 self.assertEquals(self.devel_potemplate.hasPluralMessage(), False)
113113
@@ -177,5 +177,465 @@
177 self.assertEquals(potmsgset, shared_potmsgset)177 self.assertEquals(potmsgset, shared_potmsgset)
178178
179179
180class TestSharingPOTemplatesByRegex(TestCaseWithFactory):
181 """Isolate tests for regular expression use in SharingSubset."""
182
183 layer = ZopelessDatabaseLayer
184
185 def setUp(self):
186 super(TestSharingPOTemplatesByRegex, self).setUp()
187
188 def _makeAndFind(self, names, name_pattern=None):
189 product = self.factory.makeProduct()
190 product.official_rosetta = True
191 trunk = product.getSeries('trunk')
192 for name in names:
193 self.factory.makePOTemplate(productseries=trunk, name=name)
194 subset = getUtility(IPOTemplateSet).getSharingSubset(product=product)
195 return [
196 template.name
197 for template in subset.getSharingPOTemplatesByRegex(name_pattern)]
198
199 def test_getSharingPOTemplatesByRegex_baseline(self):
200 # Baseline test.
201 self.assertContentEqual(
202 ['foo', 'foo-bar', 'foo-two'],
203 self._makeAndFind(['foo', 'foo-bar', 'foo-two'], 'foo.*'))
204
205 def test_getSharingPOTemplatesByRegex_not_all(self):
206 # A template may not match.
207 self.assertContentEqual(
208 ['foo-bar', 'foo-two'],
209 self._makeAndFind(['foo', 'foo-bar', 'foo-two'], 'foo-.*'))
210
211 def test_getSharingPOTemplatesByRegex_all(self):
212 # Not passing a pattern returns all templates.
213 self.assertContentEqual(
214 ['foo', 'foo-bar', 'foo-two'],
215 self._makeAndFind(['foo', 'foo-bar', 'foo-two']))
216
217 def test_getSharingPOTemplatesByRegex_no_match(self):
218 # A not matching pattern returns no templates.
219 self.assertContentEqual(
220 [],
221 self._makeAndFind(['foo', 'foo-bar', 'foo-two'], "doo.+dle"))
222
223 def test_getSharingPOTemplatesByRegex_robustness_single_quotes(self):
224 # Single quotes do not confuse the regex match.
225 self.assertContentEqual(
226 [],
227 self._makeAndFind(['foo', 'foo-bar', 'foo-two'], "'"))
228
229 def test_getSharingPOTemplatesByRegex_robustness_double_quotes(self):
230 # Double quotes do not confuse the regex match.
231 self.assertContentEqual(
232 [],
233 self._makeAndFind(['foo', 'foo-bar', 'foo-two'], '"'))
234
235 def test_getSharingPOTemplatesByRegex_robustness_backslash(self):
236 # A backslash at the end could escape enclosing quotes without
237 # proper escaping, leading to a SyntaxError or even a successful
238 # exploit. Instead, storm should complain about an invalid expression
239 # by raising DataError.
240 product = self.factory.makeProduct()
241 subset = getUtility(IPOTemplateSet).getSharingSubset(product=product)
242 self.assertRaises(
243 DataError, list, subset.getSharingPOTemplatesByRegex("foo.*\\"))
244
245
246class TestMessageSharingProductPackage(TestCaseWithFactory):
247 """Test message sharing between a product and a package.
248
249 Each test uses assertStatementCount to make sure the number of SQL
250 queries does not change. This was integrated here to avoid having
251 a second test case just for statement counts.
252 The current implementation is good and only needs one statement.
253 """
254
255 layer = ZopelessDatabaseLayer
256
257 def setUp(self):
258 super(TestMessageSharingProductPackage, self).setUp()
259
260 self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
261 self.hoary = self.ubuntu['hoary']
262 self.warty = self.ubuntu['warty']
263 self.ubuntu.translation_focus = self.hoary
264 self.packagename = self.factory.makeSourcePackageName()
265
266 self.product = self.factory.makeProduct()
267 self.trunk = self.product.getSeries('trunk')
268 self.stable = self.factory.makeProductSeries(
269 product=self.product)
270
271 self.templatename = self.factory.getUniqueString()
272 self.trunk_template = self.factory.makePOTemplate(
273 productseries=self.trunk, name=self.templatename)
274 self.hoary_template = self.factory.makePOTemplate(
275 distroseries=self.hoary, sourcepackagename=self.packagename,
276 name=self.templatename)
277
278 self.owner = self.factory.makePerson()
279 self.potemplateset = getUtility(IPOTemplateSet)
280
281 def _assertStatements(self, no_of_statements, resultset):
282 """Assert constant number of SQL statements when iterating result set.
283
284 This encapsulates using the 'list' function to feed the iterator to
285 the assert method. This iterates the resultset, triggering SQL
286 statement execution."""
287 return self.assertStatementCount(no_of_statements, list, resultset)
288
289 def test_getSharingPOTemplates_product(self):
290 # Sharing templates for a product include the same templates from
291 # a linked source package.
292 self.factory.makeSourcePackagePublishingHistory(
293 sourcepackagename=self.packagename,
294 distroseries=self.hoary)
295 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
296 subset = self.potemplateset.getSharingSubset(product=self.product)
297 templates = self._assertStatements(
298 1, subset.getSharingPOTemplates(self.templatename))
299
300 self.assertContentEqual(
301 [self.trunk_template, self.hoary_template], templates)
302
303 def test_getSharingPOTemplates_package(self):
304 # Sharing templates for a source package include the same templates
305 # from a linked product.
306 sourcepackage = self.factory.makeSourcePackage(
307 self.packagename, self.hoary)
308 sourcepackage.setPackaging(self.trunk, self.owner)
309 subset = self.potemplateset.getSharingSubset(
310 distribution=self.ubuntu,
311 sourcepackagename=self.packagename)
312 templates = self._assertStatements(
313 1, subset.getSharingPOTemplates(self.templatename))
314
315 self.assertContentEqual(
316 [self.trunk_template, self.hoary_template], templates)
317
318 def test_getSharingPOTemplates_product_multiple_series(self):
319 # Sharing templates for a product include the same templates from
320 # a linked source package, even from multiple product series.
321 # But templates for the same sourcepackagename are not returned
322 # if they are not linked.
323 stable_template = self.factory.makePOTemplate(
324 productseries=self.stable, name=self.templatename)
325 # This will not be returned.
326 self.factory.makePOTemplate(
327 distroseries=self.warty, sourcepackagename=self.packagename,
328 name=self.templatename)
329 self.factory.makeSourcePackagePublishingHistory(
330 sourcepackagename=self.packagename,
331 distroseries=self.hoary)
332 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
333 subset = self.potemplateset.getSharingSubset(product=self.product)
334 templates = self._assertStatements(
335 1, subset.getSharingPOTemplates(self.templatename))
336
337 self.assertContentEqual(
338 [self.trunk_template, self.hoary_template, stable_template],
339 templates)
340
341 def test_getSharingPOTemplates_package_multiple_series(self):
342 # Sharing templates for a source package include the same templates
343 # from a linked product, even with multiple product series.
344 # To include templates from other source packages, the product must
345 # be linked to that one, too.
346 stable_template = self.factory.makePOTemplate(
347 productseries=self.stable, name=self.templatename)
348 warty_template = self.factory.makePOTemplate(
349 distroseries=self.warty, sourcepackagename=self.packagename,
350 name=self.templatename)
351 hoary_sourcepackage = self.factory.makeSourcePackage(
352 self.packagename, self.hoary)
353 hoary_sourcepackage.setPackaging(self.trunk, self.owner)
354 warty_sourcepackage = self.factory.makeSourcePackage(
355 self.packagename, self.warty)
356 warty_sourcepackage.setPackaging(self.stable, self.owner)
357 subset = self.potemplateset.getSharingSubset(
358 distribution=self.ubuntu,
359 sourcepackagename=self.packagename)
360 templates = self._assertStatements(
361 1, subset.getSharingPOTemplates(self.templatename))
362
363 self.assertContentEqual(
364 [self.trunk_template, self.hoary_template,
365 stable_template, warty_template],
366 templates)
367
368 def test_getSharingPOTemplates_package_name_changed(self):
369 # When the name of a package changes (but not the name of the
370 # template), it will still share translations if it is linked
371 # to the same product.
372 changed_name = self.factory.makeSourcePackageName()
373 warty_template = self.factory.makePOTemplate(
374 distroseries=self.warty, sourcepackagename=changed_name,
375 name=self.templatename)
376 hoary_sourcepackage = self.factory.makeSourcePackage(
377 self.packagename, self.hoary)
378 hoary_sourcepackage.setPackaging(self.trunk, self.owner)
379 warty_sourcepackage = self.factory.makeSourcePackage(
380 changed_name, self.warty)
381 warty_sourcepackage.setPackaging(self.stable, self.owner)
382 subset = self.potemplateset.getSharingSubset(
383 distribution=self.ubuntu,
384 sourcepackagename=self.packagename)
385 templates = self._assertStatements(
386 1, subset.getSharingPOTemplates(self.templatename))
387
388 self.assertContentEqual(
389 [self.trunk_template, self.hoary_template, warty_template],
390 templates)
391
392 def test_getSharingPOTemplates_many_series(self):
393 # The number of queries for a call to getSharingPOTemplates must
394 # remain constant.
395
396 all_templates = [self.trunk_template, self.hoary_template]
397 hoary_sourcepackage = self.factory.makeSourcePackage(
398 self.packagename, self.hoary)
399 hoary_sourcepackage.setPackaging(self.trunk, self.owner)
400 # Add a greater number of series and sharing templates on either side.
401 seriesnames = (
402 ('0.1', 'feisty'),
403 ('0.2', 'gutsy'),
404 ('0.3', 'hardy'),
405 ('0.4', 'intrepid'),
406 ('0.5', 'jaunty'),
407 ('0.6', 'karmic'),
408 )
409 for pseries_name, dseries_name in seriesnames:
410 productseries = self.factory.makeProductSeries(
411 self.product, pseries_name)
412 all_templates.append(self.factory.makePOTemplate(
413 productseries=productseries, name=self.templatename))
414 distroseries = self.factory.makeDistroSeries(
415 self.ubuntu, name=dseries_name)
416 all_templates.append(self.factory.makePOTemplate(
417 distroseries=distroseries, sourcepackagename=self.packagename,
418 name=self.templatename))
419 sourcepackage = self.factory.makeSourcePackage(
420 self.packagename, distroseries)
421 sourcepackage.setPackaging(productseries, self.owner)
422 # Don't forget warty and stable.
423 all_templates.append(self.factory.makePOTemplate(
424 productseries=self.stable, name=self.templatename))
425 all_templates.append(self.factory.makePOTemplate(
426 distroseries=self.warty, sourcepackagename=self.packagename,
427 name=self.templatename))
428 warty_sourcepackage = self.factory.makeSourcePackage(
429 self.packagename, self.warty)
430 warty_sourcepackage.setPackaging(self.stable, self.owner)
431
432 # Looking from the product side.
433 subset = self.potemplateset.getSharingSubset(product=self.product)
434 templates = self._assertStatements(
435 1, subset.getSharingPOTemplates(self.templatename))
436 self.assertContentEqual(all_templates, templates)
437
438 # Looking from the sourcepackage side.
439 subset = self.potemplateset.getSharingSubset(
440 distribution=self.ubuntu,
441 sourcepackagename=self.packagename)
442 templates = self._assertStatements(
443 1, subset.getSharingPOTemplates(self.templatename))
444 self.assertContentEqual(all_templates, templates)
445
446 def test_getSharingPOTemplates_product_unrelated_templates(self):
447 # Sharing templates for a product must not include other templates
448 # from a linked source package.
449 self.factory.makePOTemplate(
450 distroseries=self.hoary, sourcepackagename=self.packagename,
451 name=self.factory.getUniqueString())
452 self.factory.makePOTemplate(
453 distroseries=self.warty, sourcepackagename=self.packagename,
454 name=self.factory.getUniqueString())
455 self.factory.makeSourcePackagePublishingHistory(
456 sourcepackagename=self.packagename,
457 distroseries=self.hoary)
458 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
459 subset = self.potemplateset.getSharingSubset(product=self.product)
460 templates = self._assertStatements(
461 1, subset.getSharingPOTemplates(self.templatename))
462
463 self.assertContentEqual(
464 [self.trunk_template, self.hoary_template],
465 templates)
466
467 def test_getSharingPOTemplates_product_different_names_and_series(self):
468 # A product may be packaged into differently named packages in
469 # different distroseries.
470 warty_packagename = self.factory.makeSourcePackageName()
471 warty_template = self.factory.makePOTemplate(
472 distroseries=self.warty, sourcepackagename=warty_packagename,
473 name=self.templatename)
474 self.factory.makeSourcePackagePublishingHistory(
475 sourcepackagename=self.packagename,
476 distroseries=self.hoary)
477 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
478 self.factory.makeSourcePackagePublishingHistory(
479 sourcepackagename=warty_packagename,
480 distroseries=self.warty)
481 self.trunk.setPackaging(self.warty, warty_packagename, self.owner)
482 subset = self.potemplateset.getSharingSubset(product=self.product)
483 templates = self._assertStatements(
484 1, subset.getSharingPOTemplates(self.templatename))
485
486 self.assertContentEqual(
487 [self.trunk_template, self.hoary_template, warty_template],
488 templates)
489
490 def test_getSharingPOTemplates_product_different_names_same_series(self):
491 # A product may be packaged into differently named packages even in
492 # the same distroseries. Must use different product series, though.
493 other_packagename = self.factory.makeSourcePackageName()
494 other_template = self.factory.makePOTemplate(
495 distroseries=self.hoary, sourcepackagename=other_packagename,
496 name=self.templatename)
497 self.factory.makeSourcePackagePublishingHistory(
498 sourcepackagename=self.packagename,
499 distroseries=self.hoary)
500 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
501 self.factory.makeSourcePackagePublishingHistory(
502 sourcepackagename=other_packagename,
503 distroseries=self.hoary)
504 self.stable.setPackaging(self.hoary, other_packagename, self.owner)
505 subset = self.potemplateset.getSharingSubset(product=self.product)
506 templates = self._assertStatements(
507 1, subset.getSharingPOTemplates(self.templatename))
508
509 self.assertContentEqual(
510 [self.trunk_template, self.hoary_template, other_template],
511 templates)
512
513 def test_getSharingPOTemplates_package_unrelated_template(self):
514 # Sharing templates for a source package must not include other
515 # templates from a linked product.
516 self.factory.makePOTemplate(
517 productseries=self.trunk, name=self.factory.getUniqueString())
518 self.factory.makePOTemplate(
519 productseries=self.stable, name=self.factory.getUniqueString())
520 sourcepackage = self.factory.makeSourcePackage(
521 self.packagename, self.hoary)
522 sourcepackage.setPackaging(self.trunk, self.owner)
523 subset = self.potemplateset.getSharingSubset(
524 distribution=self.ubuntu,
525 sourcepackagename=self.packagename)
526 templates = self._assertStatements(
527 1, subset.getSharingPOTemplates(self.templatename))
528
529 self.assertContentEqual(
530 [self.trunk_template, self.hoary_template],
531 templates)
532
533 def test_getSharingPOTemplates_package_unrelated_template_linked(self):
534 # Sharing templates for a source package must not include templates
535 # from sourcepackages of the same name that are linked to a different
536 # product.
537 other_productseries = self.factory.makeProductSeries()
538 other_sourcepackage = self.factory.makeSourcePackage(
539 self.packagename, self.warty)
540 other_sourcepackage.setPackaging(other_productseries, self.owner)
541 other_template = self.factory.makePOTemplate(
542 productseries=other_productseries, name=self.templatename)
543
544 sourcepackage = self.factory.makeSourcePackage(
545 self.packagename, self.hoary)
546 sourcepackage.setPackaging(self.trunk, self.owner)
547 subset = self.potemplateset.getSharingSubset(
548 distribution=self.ubuntu,
549 sourcepackagename=self.packagename)
550 templates = self._assertStatements(
551 1, subset.getSharingPOTemplates(self.templatename))
552
553 self.assertContentEqual(
554 [self.trunk_template, self.hoary_template], templates)
555
556 # The behavior is controlled by the translation focus of the
557 # distribution. The series in focus will be selected.
558 self.ubuntu.translation_focus = self.warty
559 subset = self.potemplateset.getSharingSubset(
560 distribution=self.ubuntu,
561 sourcepackagename=self.packagename)
562 templates = self._assertStatements(
563 1, subset.getSharingPOTemplates(self.templatename))
564
565 self.assertContentEqual([other_template], templates)
566
567 def test_getSharingPOTemplates_package_only(self):
568 # Sharing templates for a source package only, is done by the
569 # sourcepackagename.
570 warty_template = self.factory.makePOTemplate(
571 distroseries=self.warty, sourcepackagename=self.packagename,
572 name=self.templatename)
573 other_series = self.factory.makeDistroSeries(self.ubuntu)
574 other_template = self.factory.makePOTemplate(
575 distroseries=other_series, sourcepackagename=self.packagename,
576 name=self.templatename)
577 subset = self.potemplateset.getSharingSubset(
578 distribution=self.ubuntu,
579 sourcepackagename=self.packagename)
580 templates = self._assertStatements(
581 1, subset.getSharingPOTemplates(self.templatename))
582
583 self.assertContentEqual(
584 [self.hoary_template, other_template, warty_template], templates)
585
586 def test_getSharingPOTemplates_package_one_linked(self):
587 # Once one a sourcepackage in a distroseries that is neither the
588 # translation focus nor the current series is linked to a product,
589 # no sharing by name is possible anymore.
590 self.factory.makePOTemplate(
591 distroseries=self.warty, sourcepackagename=self.packagename,
592 name=self.templatename)
593 other_series = self.factory.makeDistroSeries(self.ubuntu)
594 self.factory.makePOTemplate(
595 distroseries=other_series, sourcepackagename=self.packagename,
596 name=self.templatename)
597 other_sourcepackage = self.factory.makeSourcePackage(
598 self.packagename, other_series)
599 other_sourcepackage.setPackaging(self.trunk, self.owner)
600
601 subset = self.potemplateset.getSharingSubset(
602 distribution=self.ubuntu,
603 sourcepackagename=self.packagename)
604 templates = self._assertStatements(
605 0, subset.getSharingPOTemplates(self.templatename))
606
607 self.assertEqual([], templates)
608
609 def test_getOrCreateSharedPOTMsgSet_product(self):
610 # Trying to create an identical POTMsgSet in a product as exists
611 # in a linked sourcepackage will return the existing POTMsgset.
612 self.factory.makeSourcePackagePublishingHistory(
613 sourcepackagename=self.packagename,
614 distroseries=self.hoary)
615 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
616 hoary_potmsgset = self.factory.makePOTMsgSet(
617 potemplate=self.hoary_template, sequence=1)
618
619 trunk_potmsgset = self.trunk_template.getOrCreateSharedPOTMsgSet(
620 singular_text=hoary_potmsgset.singular_text,
621 plural_text=hoary_potmsgset.plural_text)
622 self.assertEqual(hoary_potmsgset, trunk_potmsgset)
623
624 def test_getOrCreateSharedPOTMsgSet_package(self):
625 # Trying to create an identical POTMsgSet in a product as exists
626 # in a linked sourcepackage will return the existing POTMsgset.
627 self.factory.makeSourcePackagePublishingHistory(
628 sourcepackagename=self.packagename,
629 distroseries=self.hoary)
630 self.trunk.setPackaging(self.hoary, self.packagename, self.owner)
631 hoary_potmsgset = self.factory.makePOTMsgSet(
632 potemplate=self.hoary_template, sequence=1)
633
634 trunk_potmsgset = self.trunk_template.getOrCreateSharedPOTMsgSet(
635 singular_text=hoary_potmsgset.singular_text,
636 plural_text=hoary_potmsgset.plural_text)
637 self.assertEqual(hoary_potmsgset, trunk_potmsgset)
638
639
180def test_suite():640def test_suite():
181 return unittest.TestLoader().loadTestsFromName(__name__)641 return unittest.TestLoader().loadTestsFromName(__name__)

Subscribers

People subscribed via source and target branches