Merge lp:~timrchavez/offspring/offspring-sync-milestones-gracefully-handle-buggy-launchpad-api into lp:offspring
- offspring-sync-milestones-gracefully-handle-buggy-launchpad-api
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kevin McDermott |
Approved revision: | 148 |
Merged at revision: | 147 |
Proposed branch: | lp:~timrchavez/offspring/offspring-sync-milestones-gracefully-handle-buggy-launchpad-api |
Merge into: | lp:offspring |
Diff against target: |
286 lines (+172/-33) 3 files modified
lib/offspring/master/scripts/sync_launchpad_project_milestones.py (+53/-25) lib/offspring/master/tests/helpers.py (+56/-5) lib/offspring/master/tests/test_utils.py (+63/-3) |
To merge this branch: | bzr merge lp:~timrchavez/offspring/offspring-sync-milestones-gracefully-handle-buggy-launchpad-api |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kevin McDermott | Approve | ||
Review via email: mp+109779@code.launchpad.net |
Commit message
Description of the change
Gracefully skip over milestones that are not created correctly due to a buggy Launchpad API.
- 148. By Timothy R. Chavez
-
Update the test to actually test against fake Launchpad milestones
to exercise more of the code.
Timothy R. Chavez (timrchavez) wrote : | # |
> #1
>
> + def test_sync_
> + """
> + Test milestone syncing does not abort with bad milestone objects.
> + """
> +
> + def test_sync_
> + """
> + Test milestone syncing does not abort with bad milestone objects.
> + """
>
> Something's not quite right, they both have the same docstring ;-)
Oops. Good catch.
>
> #2
>
> I appreciate that this is a hangover from the previous implementation.
>
> + try:
> + credentials.
> + launchpad = Launchpad(
> + except:
> + launchpad = Launchpad.
> + 'Offspring Image Build System', LPNET_SERVICE_ROOT, cachedir)
> + launchpad.
> + file(config.
>
> the bare except should be fixed with something a bit more appropriate...
>
I agree with you that bare excepts are trouble, but I'm addressing a specific problem with this MP. I've filed a separate bug to address them: https:/
> #3
>
> I'd like to see a test with a successful run of the script...that doesn't have
> to hit Launchpad, but there should be a test with sync_milestones returning
> something other than None
Not sure I follow here. I re-arranged the script at little as possible to write tests that were specifically designed to validate the fix contained in this MP (though one could argue that only the second test is needed to accomplish this). The point is, an invalid milestone (retrieved from Launchpad and simulated in the second test) should not cause the script to abort. By returning 'None', we prove that we did not abort, and the function returned. I think any other tests fall outside the scope of this MP an will require additional changes to the script (if I understand your request correctly).
I pushed a new branch which makes the first test more solid (actually tests a valid milestone scenario).
Kevin McDermott (bigkevmcd) wrote : | # |
Looks good to me, +1
- 149. By Timothy R. Chavez
-
Get rid if superfulous db commit action and extra space at
the bottom of sync_launchpad_project_ milestones. py
Preview Diff
1 | === modified file 'lib/offspring/master/scripts/sync_launchpad_project_milestones.py' |
2 | --- lib/offspring/master/scripts/sync_launchpad_project_milestones.py 2012-04-19 19:41:36 +0000 |
3 | +++ lib/offspring/master/scripts/sync_launchpad_project_milestones.py 2012-06-13 14:30:26 +0000 |
4 | @@ -4,6 +4,7 @@ |
5 | # GNU Affero General Public License version 3 (see the file LICENSE). |
6 | |
7 | import os |
8 | +import sys |
9 | |
10 | from launchpadlib.credentials import Credentials |
11 | from launchpadlib.launchpad import Launchpad |
12 | @@ -17,24 +18,7 @@ |
13 | Release |
14 | ) |
15 | |
16 | -def run(): |
17 | - # Setup connection to launchpad |
18 | - config = get_configuration() |
19 | - cachedir = os.path.expanduser("~/.launchpadlib/cache/") |
20 | - credentials = Credentials() |
21 | - try: |
22 | - credentials.load(open(config.get("master", "launchpad_oauth"))) |
23 | - launchpad = Launchpad(credentials, LPNET_SERVICE_ROOT) |
24 | - except: |
25 | - launchpad = Launchpad.get_token_and_login( |
26 | - 'Offspring Image Build System', LPNET_SERVICE_ROOT, cachedir) |
27 | - launchpad.credentials.save( |
28 | - file(config.get("master", "launchpad_oauth"), "w")) |
29 | - |
30 | - # Setup connection to database |
31 | - database = create_database(config.get("master", "db")) |
32 | - store = Store(database) |
33 | - |
34 | +def sync_milestones(launchpad, store): |
35 | for launchpad_project in store.find(LaunchpadProject): |
36 | # Get launchpad instance of the project |
37 | try: |
38 | @@ -50,10 +34,19 @@ |
39 | all_milestones = p.all_milestones |
40 | |
41 | # Create new and update existing milestones. |
42 | + sys.stdout.write("I: Creating / updating existing milestones...\n") |
43 | + sys.stdout.flush() |
44 | for m in all_milestones: |
45 | - result = store.find(LaunchpadProjectMilestone, |
46 | - LaunchpadProjectMilestone.launchpad_project == launchpad_project, |
47 | - LaunchpadProjectMilestone.name == unicode(m.name)) |
48 | + try: |
49 | + result = store.find(LaunchpadProjectMilestone, |
50 | + LaunchpadProjectMilestone.launchpad_project == launchpad_project, |
51 | + LaunchpadProjectMilestone.name == unicode(m.name)) |
52 | + except AttributeError: |
53 | + sys.stdout.write( |
54 | + "W: Could not retrieve the milestone from Launchpad " |
55 | + "at '%s'\n" % m) |
56 | + sys.stdout.flush() |
57 | + continue |
58 | if result.is_empty(): |
59 | milestone = LaunchpadProjectMilestone( |
60 | m.name, m.code_name, launchpad_project, m.date_targeted, |
61 | @@ -71,10 +64,25 @@ |
62 | store.commit() |
63 | |
64 | # Delete milestones that no longer exist. |
65 | - rescindedMilestones = set(store.find(LaunchpadProjectMilestone, |
66 | - LaunchpadProjectMilestone.launchpad_project == launchpad_project |
67 | - ).values(LaunchpadProjectMilestone.name) |
68 | - ).difference(set([m.name for m in all_milestones])) |
69 | + sys.stdout.write("I: Deleting milestones that no longer exist...\n") |
70 | + sys.stdout.flush() |
71 | + |
72 | + milestones = store.find( |
73 | + LaunchpadProjectMilestone, |
74 | + LaunchpadProjectMilestone.launchpad_project == launchpad_project |
75 | + ) |
76 | + |
77 | + try: |
78 | + rescindedMilestones = set( |
79 | + milestones.values(LaunchpadProjectMilestone.name) |
80 | + ).difference(set([m.name for m in all_milestones])) |
81 | + except AttributeError: |
82 | + sys.stdout.write( |
83 | + "W: Could not retrieve the milestone from Launchpad " |
84 | + "at '%s'\n" % m) |
85 | + sys.stdout.flush() |
86 | + continue |
87 | + |
88 | for rescindedMilestoneName in rescindedMilestones: |
89 | result = store.find( |
90 | LaunchpadProjectMilestone, |
91 | @@ -87,3 +95,23 @@ |
92 | ).set(milestone_id=None) |
93 | result.remove() |
94 | store.commit() |
95 | + |
96 | +def run(): |
97 | + # Setup connection to launchpad |
98 | + config = get_configuration() |
99 | + cachedir = os.path.expanduser("~/.launchpadlib/cache/") |
100 | + credentials = Credentials() |
101 | + try: |
102 | + credentials.load(open(config.get("master", "launchpad_oauth"))) |
103 | + launchpad = Launchpad(credentials, LPNET_SERVICE_ROOT) |
104 | + except: |
105 | + launchpad = Launchpad.get_token_and_login( |
106 | + 'Offspring Image Build System', LPNET_SERVICE_ROOT, cachedir) |
107 | + launchpad.credentials.save( |
108 | + file(config.get("master", "launchpad_oauth"), "w")) |
109 | + |
110 | + # Setup connection to database |
111 | + database = create_database(config.get("master", "db")) |
112 | + store = Store(database) |
113 | + |
114 | + sync_milestones(launchpad, store) |
115 | |
116 | === modified file 'lib/offspring/master/tests/helpers.py' |
117 | --- lib/offspring/master/tests/helpers.py 2012-04-10 10:18:46 +0000 |
118 | +++ lib/offspring/master/tests/helpers.py 2012-06-13 14:30:26 +0000 |
119 | @@ -26,6 +26,26 @@ |
120 | self.mails.append((sender, rcpt, msg)) |
121 | |
122 | |
123 | +class FakeLaunchpadMilestone(object): |
124 | + def __init__(self, name): |
125 | + self.name = name |
126 | + self.code_name = name |
127 | + self.date_targeted = None |
128 | + self.is_active = True |
129 | + |
130 | + def __str__(self): |
131 | + return self.code_name |
132 | + |
133 | + |
134 | +class FakeLaunchpadProject(object): |
135 | + def __init__(self, title, milestone): |
136 | + self.title = title |
137 | + self.all_milestones = [milestone] |
138 | + |
139 | + def __str__(self): |
140 | + return self.title |
141 | + |
142 | + |
143 | class FakeBuilder(object): |
144 | def __init__(self, current_state, build_result=None): |
145 | self.name = u"test-builder" |
146 | @@ -170,7 +190,7 @@ |
147 | |
148 | self.connection.execute( |
149 | "CREATE TABLE buildresults (" |
150 | - "id SERIAL NOT NULL, " |
151 | + "id serial NOT NULL PRIMARY KEY, " |
152 | "name VARCHAR(200), " |
153 | "builder_id INTEGER, " |
154 | "project_name VARCHAR(200) NOT NULL, " |
155 | @@ -198,6 +218,36 @@ |
156 | "id SERIAL NOT NULL PRIMARY KEY, " |
157 | "dailybuildorder_id integer NOT NULL, " |
158 | "project_id VARCHAR(200) NOT NULL)") |
159 | + |
160 | + self.connection.execute( |
161 | + "CREATE TABLE launchpad_projects ( " |
162 | + "name varchar(200) NOT NULL PRIMARY KEY, " |
163 | + "title varchar(200)) ") |
164 | + |
165 | + self.connection.execute( |
166 | + "CREATE TABLE launchpad_project_milestones (" |
167 | + "id serial NOT NULL PRIMARY KEY, " |
168 | + "name varchar(200) NOT NULL, " |
169 | + "launchpad_project_id varchar(200) REFERENCES " |
170 | + "launchpad_projects (name) DEFERRABLE INITIALLY DEFERRED, " |
171 | + "title varchar(200) NOT NULL, " |
172 | + "date_targeted date, " |
173 | + "active boolean NOT NULL)") |
174 | + |
175 | + self.connection.execute( |
176 | + "CREATE TABLE releases ( " |
177 | + "build_id integer NOT NULL PRIMARY KEY REFERENCES buildresults (id) DEFERRABLE INITIALLY DEFERRED, " |
178 | + "name varchar(200) NOT NULL, " |
179 | + "milestone_id integer REFERENCES launchpad_project_milestones (id) DEFERRABLE INITIALLY DEFERRED, " |
180 | + "tag varchar(5), " |
181 | + "creator_id integer NOT NULL REFERENCES auth_user (id) DEFERRABLE INITIALLY DEFERRED, " |
182 | + "created_at timestamp with time zone NOT NULL, " |
183 | + "updated_at timestamp with time zone NOT NULL, " |
184 | + "published_at timestamp with time zone, " |
185 | + "checklist_url varchar(200) NOT NULL, " |
186 | + "notes text NOT NULL, " |
187 | + "status smallint CHECK (status >= 0) NOT NULL)") |
188 | + |
189 | self.connection.commit() |
190 | |
191 | def create_db_store(self): |
192 | @@ -208,12 +258,13 @@ |
193 | self.db_store.close() |
194 | |
195 | def drop_tables(self): |
196 | - for table in ["auth_user", "projects", "lexbuilders", |
197 | - "project_notification_subscriptions", |
198 | + for table in ["auth_user", "releases", "launchpad_projects", "projects", |
199 | + "lexbuilders", "project_notification_subscriptions", |
200 | "buildresults", "buildrequests", "dailybuildorders", |
201 | - "dailybuildorders_projects"]: |
202 | + "dailybuildorders_projects", |
203 | + "launchpad_project_milestones"]: |
204 | try: |
205 | - self.connection.execute("DROP TABLE %s" % table) |
206 | + self.connection.execute("DROP TABLE %s CASCADE" % table) |
207 | self.connection.commit() |
208 | except: |
209 | self.connection.rollback() |
210 | |
211 | === modified file 'lib/offspring/master/tests/test_utils.py' |
212 | --- lib/offspring/master/tests/test_utils.py 2011-12-02 09:03:14 +0000 |
213 | +++ lib/offspring/master/tests/test_utils.py 2012-06-13 14:30:26 +0000 |
214 | @@ -7,9 +7,69 @@ |
215 | from offspring.master.utils import ( |
216 | build_active_projects, process_pending_build_orders) |
217 | from offspring.master.models import ( |
218 | - BuildRequest, Project, DailyBuildOrder, DailyBuildOrderProject) |
219 | - |
220 | -from offspring.master.tests.helpers import BaseOffspringMasterTestCase |
221 | + BuildRequest, |
222 | + Project, |
223 | + DailyBuildOrder, |
224 | + DailyBuildOrderProject, |
225 | + LaunchpadProject, |
226 | + LaunchpadProjectMilestone) |
227 | +from offspring.master.scripts import sync_launchpad_project_milestones |
228 | + |
229 | +from offspring.master.tests.helpers import ( |
230 | + BaseOffspringMasterTestCase, |
231 | + FakeLaunchpadProject, |
232 | + FakeLaunchpadMilestone |
233 | +) |
234 | + |
235 | +class TestSyncLaunchpadProjectMilestones(BaseOffspringMasterTestCase): |
236 | + |
237 | + def test_sync_project_milestones(self): |
238 | + """ |
239 | + Test milestone syncing succeeds. |
240 | + """ |
241 | + lp_project = LaunchpadProject() |
242 | + lp_project.name = u"test-project" |
243 | + self.db_store.add(lp_project) |
244 | + |
245 | + lp_project_milestone = LaunchpadProjectMilestone( |
246 | + u"test-project-1.0", u"test-project-1.0", lp_project) |
247 | + self.db_store.add(lp_project_milestone) |
248 | + |
249 | + launchpad = self.mocker.mock() |
250 | + launchpad.projects["test-project"] |
251 | + fake_lp_milestone = FakeLaunchpadMilestone(u"test-project-1.0") |
252 | + self.mocker.result(FakeLaunchpadProject(u"test-project", fake_lp_milestone)) |
253 | + self.mocker.replay() |
254 | + |
255 | + self.assertIs( |
256 | + sync_launchpad_project_milestones.sync_milestones( |
257 | + launchpad, self.db_store), None) |
258 | + |
259 | + |
260 | + def test_sync_project_milestones_handles_bad_milestone(self): |
261 | + """ |
262 | + Test milestone syncing does not abort with bad milestone objects. |
263 | + """ |
264 | + lp_project = LaunchpadProject() |
265 | + lp_project.name = u"test-project" |
266 | + self.db_store.add(lp_project) |
267 | + |
268 | + lp_project_milestone = LaunchpadProjectMilestone( |
269 | + u"test-project-1.0", u"test-project-1.0", lp_project) |
270 | + self.db_store.add(lp_project_milestone) |
271 | + |
272 | + launchpad = self.mocker.mock() |
273 | + launchpad.projects[u"test-project"] |
274 | + fake_lp_milestone = FakeLaunchpadMilestone(u"test-project-1.0") |
275 | + delattr(fake_lp_milestone, "name") |
276 | + fake_lp_project = FakeLaunchpadProject( |
277 | + u"test-project", fake_lp_milestone) |
278 | + self.mocker.result(fake_lp_project) |
279 | + self.mocker.replay() |
280 | + |
281 | + self.assertIs( |
282 | + sync_launchpad_project_milestones.sync_milestones( |
283 | + launchpad, self.db_store), None) |
284 | |
285 | |
286 | class TestBuildActiveProjects(BaseOffspringMasterTestCase): |
#1
+ def test_sync_ project_ milestones( self): project_ milestones_ handles_ bad_milestone( self):
+ """
+ Test milestone syncing does not abort with bad milestone objects.
+ """
+
+ def test_sync_
+ """
+ Test milestone syncing does not abort with bad milestone objects.
+ """
Something's not quite right, they both have the same docstring ;-)
#2
I appreciate that this is a hangover from the previous implementation. ..but.. .
+ try: load(open( config. get("master" , "launchpad_ oauth") )) credentials, LPNET_SERVICE_ROOT) get_token_ and_login( credentials. save( get("master" , "launchpad_oauth"), "w"))
+ credentials.
+ launchpad = Launchpad(
+ except:
+ launchpad = Launchpad.
+ 'Offspring Image Build System', LPNET_SERVICE_ROOT, cachedir)
+ launchpad.
+ file(config.
the bare except should be fixed with something a bit more appropriate...
#3
I'd like to see a test with a successful run of the script...that doesn't have to hit Launchpad, but there should be a test with sync_milestones returning something other than None