Merge lp:~jtv/launchpad/db-bug-435655 into lp:launchpad/db-devel

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/db-bug-435655
Merge into: lp:launchpad/db-devel
Diff against target: 108 lines
3 files modified
lib/lp/translations/doc/translations-export-to-branch.txt (+0/-1)
lib/lp/translations/scripts/tests/test_translations_to_branch.py (+6/-1)
lib/lp/translations/scripts/translations_to_branch.py (+23/-2)
To merge this branch: bzr merge lp:~jtv/launchpad/db-bug-435655
Reviewer Review Type Date Requested Status
Brad Crittenden (community) release-critical Approve
Michael Nelson (community) code Approve
Review via email: mp+12333@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

= Bug 435655 =

This branch fixes critical breakage in the translations-to-branch export
script.

In 3.0 we gave this script the ability to skip exports for POFiles in a
branch that hadn't changed since their previous export run. This change
broke because the timestamp on the bzr revision that we got this
information from is not a datetime. It's a float representing seconds
since the epoch, UTC-based.

As it turns out there was a test that exercised this code, and it did
trigger the exception, but since it happened in a script run and was
handled there, the exception did not propagate into the test. Explicit
checks of the script run's results were needed.

I had to do some reverse engineering to figure out the details. As it
turns out, even the bzr experts did not know exactly how to interpret
and convert the timestamp.

You may notice that I also made the script avoid its commit when there
are no changes. This is mostly duplicative of a similar check further
down the call tree, in DirectBranchCommit. But it also circumvents the
check for commit conflicts, which simplifies the tests. Otherwise, I'd
either have to patch up the last_scanned_id for the branch object, or
let the test spit out a "branch has changed" error when it's really not
supposed to change anything in the branch anyway.

Test:
{{{
./bin/test -vv -t translations.*to.branch
}}}

Q/A is difficult, since this script must run on the codehosting server
where the branches are hosted. This pretty much rules out staging.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (6.9 KiB)

Great stuff jtv.
r=me - two questions below.

Thanks!

> = Bug 435655 =
>
> This branch fixes critical breakage in the translations-to-branch export
> script.
>
> In 3.0 we gave this script the ability to skip exports for POFiles in a
> branch that hadn't changed since their previous export run. This change
> broke because the timestamp on the bzr revision that we got this
> information from is not a datetime. It's a float representing seconds
> since the epoch, UTC-based.
>
> As it turns out there was a test that exercised this code, and it did
> trigger the exception, but since it happened in a script run and was
> handled there, the exception did not propagate into the test. Explicit
> checks of the script run's results were needed.

Great! OK, the setup in your test has convinced me that translations is
the new soyuz ;). I've just annotated your changes to ensure I've
understood the situation correctly.

>
> I had to do some reverse engineering to figure out the details. As it
> turns out, even the bzr experts did not know exactly how to interpret
> and convert the timestamp.
>
> You may notice that I also made the script avoid its commit when there
> are no changes. This is mostly duplicative of a similar check further
> down the call tree, in DirectBranchCommit. But it also circumvents the
> check for commit conflicts, which simplifies the tests. Otherwise, I'd
> either have to patch up the last_scanned_id for the branch object, or
> let the test spit out a "branch has changed" error when it's really not
> supposed to change anything in the branch anyway.
>
> Test:
> {{{
> ./bin/test -vv -t translations.*to.branch
> }}}
>
> Q/A is difficult, since this script must run on the codehosting server
> where the branches are hosted. This pretty much rules out staging.

> === modified file 'lib/lp/translations/doc/translations-export-to-branch.txt'
> --- lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-16 06:57:35 +0000
> +++ lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-24 08:57:28 +0000
> @@ -187,7 +187,6 @@
> INFO Exporting to translations branches.
> INFO Exporting Gazblachko trunk series.
> DEBUG Last commit was at ....
> - INFO Committed 0 file(s).
> INFO Unlock.
> INFO Processed 1 item(s); 0 failure(s).
>

OK, so this is due to skipping the commit when there are no changes.

>
> === modified file 'lib/lp/translations/scripts/tests/test_translations_to_branch.py'
> --- lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-09-15 15:30:59 +0000
> +++ lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-09-24 08:57:28 +0000
> @@ -111,8 +111,11 @@
> # anything because it sees that the POFile has not been changed
> # since the last export.
> retcode, stdout, stderr = run_script(
> - 'cronscripts/translations-export-to-branch.py', ['-vvv'])
> + 'cronscripts/translations-export-to-branch.py',
> + ['-vvv', '--no-fudge'])
> + self.assertEqual(0, retcode)
> self.assertIn('Last commit was at', stderr)
> + self.assertIn("Processed 1 item(s); 0 failure(s)....

Read more...

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (3.5 KiB)

> Great stuff jtv.
> r=me - two questions below.

Thanks for the fast review! Read on...

> > === modified file 'lib/lp/translations/doc/translations-export-to-
> branch.txt'
> > --- lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-16
> 06:57:35 +0000
> > +++ lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-24
> 08:57:28 +0000
> > @@ -187,7 +187,6 @@
> > INFO Exporting to translations branches.
> > INFO Exporting Gazblachko trunk series.
> > DEBUG Last commit was at ....
> > - INFO Committed 0 file(s).
> > INFO Unlock.
> > INFO Processed 1 item(s); 0 failure(s).
> >
>
> OK, so this is due to skipping the commit when there are no changes.

Yes.

> > === modified file
> 'lib/lp/translations/scripts/tests/test_translations_to_branch.py'
> > --- lib/lp/translations/scripts/tests/test_translations_to_branch.py
> 2009-09-15 15:30:59 +0000
> > +++ lib/lp/translations/scripts/tests/test_translations_to_branch.py
> 2009-09-24 08:57:28 +0000
> > @@ -111,8 +111,11 @@
> > # anything because it sees that the POFile has not been changed
> > # since the last export.
> > retcode, stdout, stderr = run_script(
> > - 'cronscripts/translations-export-to-branch.py', ['-vvv'])
> > + 'cronscripts/translations-export-to-branch.py',
> > + ['-vvv', '--no-fudge'])
> > + self.assertEqual(0, retcode)
> > self.assertIn('Last commit was at', stderr)
> > + self.assertIn("Processed 1 item(s); 0 failure(s).", stderr)
> >
>
> Right - so now you're checking that it didn't error, and that it did
> process an item. Is it worth here explicitely asserting that
> 'INFO Committed' is not in stderr? (It's already part of the documentation
> above.)

Excellent idea! I'm going with it. Using regexes to search for it.

> > === modified file 'lib/lp/translations/scripts/translations_to_branch.py'
> > --- lib/lp/translations/scripts/translations_to_branch.py 2009-09-15
> 15:30:59 +0000
> > +++ lib/lp/translations/scripts/translations_to_branch.py 2009-09-24

> > @@ -81,6 +88,13 @@
> > """Is `revision` an automatic translations commit?"""
> > return revision.message == self.commit_message
> >
> > + def _getRevisionTime(self, revision):
> > + """Get timestamp of `revision`."""
> > + # The bzr timestamp is a float representing UTC-based seconds
> > + # since the epoch. It stores the timezone as well, but we can
> > + # ignore it here.
> > + return datetime.fromtimestamp(revision.timestamp, UTC)
> > +
>
> Is this a separate method because you thought you'd need to re-use it? or
> just to encapsulate the comment? Normally I'd not do that for a single
> line unless it was being re-used. It could simply go in the method below
> right?

It's mostly because getting it right turned out to be difficult. If even
lifeless doesn't know how to get a piece of information from bzrlib, when
you do finally figure it out, it's worth isolating!

I know, it doesn't look like much. But what we have in the revision is: a
"timestamp" of unclear type, and a "timezone" which isn't a timezone but an
...

Read more...

Revision history for this message
Brad Crittenden (bac) :
review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/translations/doc/translations-export-to-branch.txt'
2--- lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-16 06:57:35 +0000
3+++ lib/lp/translations/doc/translations-export-to-branch.txt 2009-09-24 09:51:12 +0000
4@@ -187,7 +187,6 @@
5 INFO Exporting to translations branches.
6 INFO Exporting Gazblachko trunk series.
7 DEBUG Last commit was at ....
8- INFO Committed 0 file(s).
9 INFO Unlock.
10 INFO Processed 1 item(s); 0 failure(s).
11
12
13=== modified file 'lib/lp/translations/scripts/tests/test_translations_to_branch.py'
14--- lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-09-15 15:30:59 +0000
15+++ lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-09-24 09:51:12 +0000
16@@ -111,8 +111,13 @@
17 # anything because it sees that the POFile has not been changed
18 # since the last export.
19 retcode, stdout, stderr = run_script(
20- 'cronscripts/translations-export-to-branch.py', ['-vvv'])
21+ 'cronscripts/translations-export-to-branch.py',
22+ ['-vvv', '--no-fudge'])
23+ self.assertEqual(0, retcode)
24 self.assertIn('Last commit was at', stderr)
25+ self.assertIn("Processed 1 item(s); 0 failure(s).", stderr)
26+ self.assertEqual(
27+ None, re.search("INFO\s+Committed [0-9]+ file", stderr))
28
29
30 def test_suite():
31
32=== modified file 'lib/lp/translations/scripts/translations_to_branch.py'
33--- lib/lp/translations/scripts/translations_to_branch.py 2009-09-15 15:30:59 +0000
34+++ lib/lp/translations/scripts/translations_to_branch.py 2009-09-24 09:51:12 +0000
35@@ -43,6 +43,13 @@
36 # factor.
37 fudge_factor = timedelta(hours=6)
38
39+ def add_my_options(self):
40+ """See `LaunchpadScript`."""
41+ self.parser.add_option(
42+ '-n', '--no-fudge', action='store_true', dest='no_fudge',
43+ default=False,
44+ help="For testing: no fudge period for POFile changes.")
45+
46 def _checkForObjections(self, source):
47 """Check for reasons why we can't commit to this branch.
48
49@@ -81,6 +88,13 @@
50 """Is `revision` an automatic translations commit?"""
51 return revision.message == self.commit_message
52
53+ def _getRevisionTime(self, revision):
54+ """Get timestamp of `revision`."""
55+ # The bzr timestamp is a float representing UTC-based seconds
56+ # since the epoch. It stores the timezone as well, but we can
57+ # ignore it here.
58+ return datetime.fromtimestamp(revision.timestamp, UTC)
59+
60 def _getLatestTranslationsCommit(self, branch):
61 """Get date of last translations commit to `branch`, if any."""
62 cutoff_date = datetime.now(UTC) - self.previous_commit_cutoff_age
63@@ -89,7 +103,7 @@
64 repository = branch.repository
65 for rev_id in repository.iter_reverse_revision_history(current_rev):
66 revision = repository.get_revision(rev_id)
67- revision_date = revision.timestamp
68+ revision_date = self._getRevisionTime(revision)
69 if self._isTranslationsCommit(revision):
70 return revision_date
71
72@@ -125,6 +139,8 @@
73 self.logger.debug("Last commit was at %s." % last_commit_date)
74 changed_since = last_commit_date - self.fudge_factor
75
76+ change_count = 0
77+
78 try:
79 subset = getUtility(IPOTemplateSet).getSubset(
80 productseries=source, iscurrent=True)
81@@ -144,6 +160,7 @@
82 pofile_contents = pofile.export()
83
84 committer.writeFile(pofile_path, pofile_contents)
85+ change_count += 1
86
87 # We're not actually writing any changes to the
88 # database, but it's not polite to stay in one
89@@ -155,7 +172,8 @@
90 # anything about it any longer.
91 template.clearPOFileCache()
92
93- self._commit(source, committer)
94+ if change_count > 0:
95+ self._commit(source, committer)
96 finally:
97 committer.unlock()
98
99@@ -193,6 +211,9 @@
100 from lp.registry.model.product import Product
101 from lp.registry.model.productseries import ProductSeries
102
103+ if self.options.no_fudge:
104+ self.fudge_factor = timedelta(0)
105+
106 self.logger.info("Exporting to translations branches.")
107
108 self.store = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)

Subscribers

People subscribed via source and target branches

to status/vote changes: