Merge lp:~milo/linaro-patchmetrics/bug1013577 into lp:linaro-patchmetrics
- bug1013577
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 363 | ||||
Proposed branch: | lp:~milo/linaro-patchmetrics/bug1013577 | ||||
Merge into: | lp:linaro-patchmetrics | ||||
Diff against target: |
394 lines (+248/-47) 4 files modified
apps/patchmetrics/bin/sync-gerrit-changes.py (+16/-9) apps/patchmetrics/gerrit.py (+167/-36) apps/patchmetrics/gerrit_json.py (+20/-0) apps/patchmetrics/tests/test_gerrit.py (+45/-2) |
||||
To merge this branch: | bzr merge lp:~milo/linaro-patchmetrics/bug1013577 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Deepti B. Kalakeri (community) | Approve | ||
Linaro Infrastructure | Pending | ||
Review via email: mp+111432@code.launchpad.net |
Commit message
Description of the change
Hello,
I would like to propose the following changes to linaro-patchmetrics in order to fix the problems we are facing with gerrit and its transition to a new REST interface from the JSON-RPC one.
The merge proposal does not fix all the problems, it is still a work in progress, since the JSON output provided by the new REST interface is different than the old one, and we need to make further changes in the code base (I need to retrieve some JSON output from an old version of Gerrit, probably using Linaro instance on Gerrit, and compare it with the new one).
Since there are already substantial changes, I'm asking for a preliminary review.
What is included in this merge proposal is:
- PEP8 fixes.
- A fix to the call invoked by the script 'sync-gerrit-
- I also wrote new tests for the new functions added.
- 363. By Milo Casagrande
-
Fixed error on test, fixed indentation.
- 364. By Milo Casagrande
-
Use the new JSON format to create the necessary objects.
Milo Casagrande (milo) wrote : | # |
Hello,
first, thanks Deepti for the first review!
I updated the code again, pushing also the final fixes for the gerrit problem.
With the latest push:
- I introduced a new python module, 'gerrit_json.py', that only holds variables that are the names used inside the Gerrit JSON output: these variables are used to retrieve the values from the output, and might be used also somewhere else in the code, that is why I created a separate module.
- I fixed all the old calls in the code that were used to access the JSON values: before we had a list of elements, now we have a list of dictionaries whose values we need.
- Cleaned up also other parts of the code, in particular:
* Use "COMPATC_JSON" as the default JSON output, so that it should make a little bit smaller and hopefully faster.
* Use "gzip" compression on the output from the server: probably needs more testing to see if it really is beneficial, but if we get back a lot of results from the server, we can save a little bit on space and gain some peed.
* Cleaned up a little bit the tests.
* Added some links as a reference to Gerrit REST API.
For the tests part, they should be called directly from the command line from django, but I have problem with my django setup: for my own tests, I created a small unittest class that calls all the necessary parts to be tested.
Milo Casagrande (milo) wrote : | # |
Oh, I forgot, I removed also one entire function that was not needed anymore: before, some of the necessary information, were retrieved via two calls to the Java Servlet: one to get the actual changes, and another one to get the details of the change. Now all the information is stored in the same output.
For a comparison of the two JSON output, look at the bug report from the merge proposal, I attached there the output we get from android-
The difference from the old approach, is that now if a user is not in the Gerrit database, we receive back an error 400, that we catch and deal with.
- 365. By Milo Casagrande
-
Fixed indentation; re-added return statement, problem with tests.
Deepti B. Kalakeri (deeptik) wrote : | # |
> Hello,
>
> first, thanks Deepti for the first review!
> I updated the code again, pushing also the final fixes for the gerrit problem.
>
> With the latest push:
> - I introduced a new python module, 'gerrit_json.py', that only holds
> variables that are the names used inside the Gerrit JSON output: these
> variables are used to retrieve the values from the output, and might be used
> also somewhere else in the code, that is why I created a separate module.
> - I fixed all the old calls in the code that were used to access the JSON
> values: before we had a list of elements, now we have a list of dictionaries
> whose values we need.
> - Cleaned up also other parts of the code, in particular:
> * Use "COMPATC_JSON" as the default JSON output, so that it should make a
> little bit smaller and hopefully faster.
> * Use "gzip" compression on the output from the server: probably needs more
> testing to see if it really is beneficial, but if we get back a lot of results
> from the server, we can save a little bit on space and gain some peed.
> * Cleaned up a little bit the tests.
> * Added some links as a reference to Gerrit REST API.
>
> For the tests part, they should be called directly from the command line from
> django, but I have problem with my django setup: for my own tests, I created a
> small unittest class that calls all the necessary parts to be tested.
It would be good if you added your test script here as well.
Milo Casagrande (milo) wrote : | # |
Hi Deepti,
I attached it to the bug report, since here it is not possible to insert files, and I would prefer not to upload it in the bzr branch, as it is not really related to the test infrastructure there. I have that file in the 'apps/patchmetr
You might need to tweak your PYTHONPATH to make it work, since it needs to pick-up other files from the project while it imports the other statements.
Otherwise, I have another file that is a copy of the modified functions in 'gerrit.py' and that is possible to test directly without importing all the rest of the project.
Deepti B. Kalakeri (deeptik) wrote : | # |
Thanks for the fixes:
161 # The request in this case needs to be a GET.
162 json_string = sanitize_
163 return json_string
It is probably good to initialize json_string in gerrit.py to None here before we called sanitize_
175 def sanitize_
176 """Cleans up the JSON response got from the REST interface, in order to be
177 valid JSON.
178
179 :param json_string: the JSON string to clean up.
180 """
181 # The JSON response is not valid JSON, there is a magic sequence at the
182 # beginning of the result. We are paranoid, and remove also other white
183 # spaces.
184 if json_string.
185 return json_string.
Are we sure we that the json_string should always start with GERRIT_
If not, then we need to return json_string even when json_string does not start with GERRIT_
Milo Casagrande (milo) wrote : | # |
> Thanks for the fixes:
>
> 161 # The request in this case needs to be a GET.
> 162 json_string =
> sanitize_
> 163 return json_string
> It is probably good to initialize json_string in gerrit.py to None here
> before we called sanitize_
Sure, fixed.
> 175 def sanitize_
> 176 """Cleans up the JSON response got from the REST interface, in order
> to be
> 177 valid JSON.
> 178
> 179 :param json_string: the JSON string to clean up.
> 180 """
> 181 # The JSON response is not valid JSON, there is a magic sequence at
> the
> 182 # beginning of the result. We are paranoid, and remove also other
> white
> 183 # spaces.
> 184 if json_string.
> 185 return json_string.
>
> Are we sure we that the json_string should always start with
> GERRIT_
> If not, then we need to return json_string even when json_string does not
> start with GERRIT_
As for Gerrit documentation, it should be. Link where it is written is here:
https:/
But better handle that case too.
Thanks.
- 366. By Milo Casagrande
-
Fixed review comments.
- 367. By Milo Casagrande
-
Return the json object, not the string.
Deepti B. Kalakeri (deeptik) wrote : | # |
+1 Looks good.
- 368. By Milo Casagrande
-
Removed unused function.
Preview Diff
1 | === modified file 'apps/patchmetrics/bin/sync-gerrit-changes.py' |
2 | --- apps/patchmetrics/bin/sync-gerrit-changes.py 2012-01-23 18:41:51 +0000 |
3 | +++ apps/patchmetrics/bin/sync-gerrit-changes.py 2012-06-25 12:32:18 +0000 |
4 | @@ -6,9 +6,14 @@ |
5 | |
6 | from patchmetrics.models import TeamMembership |
7 | from patchmetrics.gerrit import ( |
8 | + GerritError, |
9 | + create_or_update_patches_from_aosp_changes, |
10 | get_aosp_changes_from, |
11 | - create_or_update_patches_from_aosp_changes, |
12 | - GerritError) |
13 | + ) |
14 | +from patchmetrics.gerrit_json import ( |
15 | + GERRIT_MORE_CHANGES, |
16 | + GERRIT_SORT_KEY, |
17 | +) |
18 | |
19 | |
20 | linaro_engineers = Person.objects.filter( |
21 | @@ -17,8 +22,8 @@ |
22 | start_after = None |
23 | while True: |
24 | try: |
25 | - result = get_aosp_changes_from( |
26 | - person.email, start_after=start_after) |
27 | + results = get_aosp_changes_from(person.email, |
28 | + start_after=start_after) |
29 | except GerritError, e: |
30 | if '%s not found' % person.email in str(e): |
31 | # Gerrit returns an error when you search for changes owned by |
32 | @@ -26,15 +31,17 @@ |
33 | # just skip to the next email. |
34 | break |
35 | raise |
36 | - at_end = result['atEnd'] |
37 | - changes = result['changes'] |
38 | + # If we get all the changes in a single batch, the GERRIT_MORE_CHANGES |
39 | + # key will not be in the JSON output, so we set it to False. |
40 | + more_changes = results[-1].get(GERRIT_MORE_CHANGES) or False |
41 | created_or_updated_patches = ( |
42 | - create_or_update_patches_from_aosp_changes(changes, person)) |
43 | + create_or_update_patches_from_aosp_changes(results, person)) |
44 | |
45 | # If there are less created/updated changes than the number of changes |
46 | # returned by Gerrit we know we can stop as they're sorted most |
47 | # recently updated first. |
48 | - if len(changes) != len(created_or_updated_patches) or at_end: |
49 | + if len(results) != len(created_or_updated_patches) or \ |
50 | + (not more_changes): |
51 | break |
52 | |
53 | - start_after = changes[-1]['sortKey'] |
54 | + start_after = results[-1].get(GERRIT_SORT_KEY) |
55 | |
56 | === modified file 'apps/patchmetrics/gerrit.py' |
57 | --- apps/patchmetrics/gerrit.py 2012-01-31 18:32:44 +0000 |
58 | +++ apps/patchmetrics/gerrit.py 2012-06-25 12:32:18 +0000 |
59 | @@ -1,14 +1,37 @@ |
60 | from datetime import datetime |
61 | import email |
62 | import urllib2 |
63 | +from urllib import urlencode |
64 | |
65 | import json |
66 | |
67 | from patchwork.models import Patch, Project, State |
68 | from patchmetrics.models import GerritChange |
69 | +from patchmetrics.gerrit_json import ( |
70 | + GERRIT_CREATED_KEY, |
71 | + GERRIT_ID_KEY, |
72 | + GERRIT_NUMBER_KEY, |
73 | + GERRIT_PROJECT_KEY, |
74 | + GERRIT_SANITIZABLE_VALUE, |
75 | + GERRIT_STATUS_KEY, |
76 | + GERRIT_SUBJECT_KEY, |
77 | + GERRIT_UPDATED_KEY, |
78 | +) |
79 | |
80 | |
81 | ROOT_URL = 'https://android-review.googlesource.com/gerrit/rpc' |
82 | +# This is the URL for the REST interface. |
83 | +ROOT_REST_URL = 'https://android-review.googlesource.com' |
84 | +# The default path for the REST interface. |
85 | +CHANGES_QUERY_PATH = '/changes/' |
86 | +# This is the query prefix used for gerrit REST interface. |
87 | +GERRIT_QUERY_PREFIX = 'q=' |
88 | +# Separator for gerrit query parameters. |
89 | +GERRIT_QUERY_SEPARATOR = '+' |
90 | +# Default separator for URL query string. |
91 | +QUERY_STRING_SEP = '&' |
92 | +# Default character for starting a query string. |
93 | +QUERY_STRING_START = '?' |
94 | # A mapping from AOSP states to our internal ones. |
95 | STATE_MAP = {'MERGED': 'Accepted', 'NEW': 'New', 'ABANDONED': 'Rejected'} |
96 | |
97 | @@ -36,32 +59,141 @@ |
98 | return response.get('result') |
99 | |
100 | |
101 | -def get_aosp_change_details(change_id): |
102 | - """Get the details of the Gerrit change with the given ID.""" |
103 | - return jsonrpc_request( |
104 | - '/ChangeDetailService', |
105 | - 'changeDetail', [dict(id=change_id)])['change'] |
106 | - |
107 | - |
108 | -def get_aosp_changes_from(email, start_after=None, page_size=100): |
109 | +def get_aosp_changes_from(email, start_after=None, page_size=25, |
110 | + return_format='JSON_COMPACT'): |
111 | """Get the list of changes authored by the given email address. |
112 | |
113 | - :param start_after: Only include changes whose sortKey is lower than |
114 | - this. Use None if you want to start from the beginning. |
115 | - :param page_size: Number of items to include in the list. |
116 | - """ |
117 | - if start_after is None: |
118 | - start_after = 'z' |
119 | - query = "owner: %s" % email |
120 | - return jsonrpc_request( |
121 | - '/ChangeListService', |
122 | - 'allQueryNext', [query, start_after, page_size]) |
123 | - |
124 | - |
125 | -def create_or_update_patches_from_aosp_changes(changes, author): |
126 | - """Create or update Patch objects from the given list of changes. |
127 | - |
128 | - For each of the changes in the list, make sure there's a matching Patch |
129 | + :param email: The email of the user to get the changes of. |
130 | + :param start_after: Only include changes whose 'sortkey_after' is lower |
131 | + than this. Use None if you want to start from the |
132 | + beginning. |
133 | + :param page_size: Number of items to include in the list, defaults to 25. |
134 | + :param return_format: The format of the response, defaults to |
135 | + 'JSON_COMPACT' for JSON format without spaces, also |
136 | + possible to specify 'JSON'. |
137 | + """ |
138 | + params = {'format': return_format, 'n': page_size} |
139 | + query_params = {'owner': email, 'sortkey_after': start_after} |
140 | + return rest_api_request(ROOT_REST_URL, CHANGES_QUERY_PATH, params, |
141 | + query_params) |
142 | + |
143 | + |
144 | +def rest_api_request(url, path, params, query_params): |
145 | + """Calls the gerrit REST interface and return the JSON response. |
146 | + |
147 | + :param url: the root URL of the web service. |
148 | + :param path: the path of the REST interface, after the URL. |
149 | + :param params: the parameters for the query, can be a dictionary or tuple. |
150 | + :param query_params: the parameters for the gerrit query interface, must |
151 | + be a dictionary. |
152 | + """ |
153 | + request_url = create_request_query_url(url, path, params, query_params) |
154 | + return gerrit_request(request_url) |
155 | + |
156 | + |
157 | +def create_request_query_url(url, path, params, query_params): |
158 | + """Creates the full query URL from the various elements. |
159 | + |
160 | + :param url: the root URL of the web service, without path to the REST |
161 | + interface. |
162 | + :param path: the path of the REST interface, after the URL. |
163 | + :param params: the parameters for the query, can be a dictionary or tuple. |
164 | + :param query_params: the parameters for the gerrit query interface, must |
165 | + be a dictionary. |
166 | + """ |
167 | + if not url.endswith('/') and not path.startswith('/'): |
168 | + url += '/' |
169 | + if not path.endswith('/'): |
170 | + path += '/' |
171 | + request_url = url + path + QUERY_STRING_START |
172 | + |
173 | + request_url += urlencode(params) |
174 | + request_url += create_gerrit_rest_query(query_params) |
175 | + |
176 | + return request_url |
177 | + |
178 | + |
179 | +def create_gerrit_rest_query(query_params): |
180 | + """Creates the gerrit query based on the passed parametes. |
181 | + |
182 | + The query result should be in the form of: |
183 | + &q=key:value+key:value[+key:value ...] |
184 | + |
185 | + :param query_params: a dictionary containing the (key,value) pairs that |
186 | + will be used for the query. |
187 | + """ |
188 | + if not isinstance(query_params, dict): |
189 | + raise TypeError("'query_params' must be of type 'dict', got '%s'" % |
190 | + type(query_params).__name__) |
191 | + |
192 | + params_list = [] |
193 | + # Where we will store the query. |
194 | + gerrit_query = '' |
195 | + for key, value in query_params.iteritems(): |
196 | + # We cannot have None values here, we just skip them. |
197 | + if value is not None: |
198 | + params_list.append(":".join((key, value))) |
199 | + # The resulting query string, without separators or prefixes, should be in |
200 | + # the form of: |
201 | + # key:value+key:value |
202 | + # This is a normal query, more complex queries need to be thought of, since |
203 | + # we can express also OR, AND and NOT operators. By default it is an AND. |
204 | + if params_list: |
205 | + gerrit_query = QUERY_STRING_SEP + GERRIT_QUERY_PREFIX + \ |
206 | + GERRIT_QUERY_SEPARATOR.join(params_list) |
207 | + |
208 | + return gerrit_query |
209 | + |
210 | + |
211 | +def gerrit_request(request_url): |
212 | + """Performs the REST call, opening the connection to the web service. |
213 | + |
214 | + :param request_url: the full URL of the request to be performed. |
215 | + """ |
216 | + json_object = None |
217 | + try: |
218 | + request = urllib2.Request(request_url) |
219 | + # Looks like, starting with gerrit version 2.5, we need to do this to |
220 | + # talk with it. More info here: |
221 | + # https://groups.google.com/group/repo-discuss/msg/78e70b07ad1aacce |
222 | + # https://gerrit-review.googlesource.com/Documentation/rest-api.html |
223 | + request.add_header("Accept", "application/json") |
224 | + request.add_header("Accept-Encoding", "gzip") |
225 | + # The request in this case needs to be a GET. |
226 | + json_string = sanitize_json_response(urllib2.urlopen(request).read()) |
227 | + json_object = json.loads(json_string) |
228 | + except urllib2.HTTPError, ex: |
229 | + # If we get back an error code 400, it probably means that what we are |
230 | + # looking for does not exist. We need to make sure of that and handle |
231 | + # it. As it is now, we raise the same expected exception with the same |
232 | + # message as before. |
233 | + if ex.code == 400: |
234 | + raise GerritError(ex.read()) |
235 | + else: |
236 | + raise(ex) |
237 | + |
238 | + return json_object |
239 | + |
240 | + |
241 | +def sanitize_json_response(json_string): |
242 | + """Cleans up the JSON response got from the REST interface, in order to be |
243 | + valid JSON. |
244 | + |
245 | + :param json_string: the JSON string to clean up. |
246 | + """ |
247 | + # The JSON response is not valid JSON, there is a magic sequence at the |
248 | + # beginning of the result. We are paranoid, and remove also other white |
249 | + # spaces. |
250 | + valid_json = json_string |
251 | + if json_string.startswith(GERRIT_SANITIZABLE_VALUE): |
252 | + valid_json = json_string.lstrip(GERRIT_SANITIZABLE_VALUE) |
253 | + return valid_json.strip() |
254 | + |
255 | + |
256 | +def create_or_update_patches_from_aosp_changes(results, author): |
257 | + """Create or update Patch objects from the given list of results. |
258 | + |
259 | + For each of the result in the list, make sure there's a matching Patch |
260 | (and GerritChange) object in our DB matching it and update it to be in |
261 | sync with the remote change. |
262 | |
263 | @@ -69,23 +201,22 @@ |
264 | """ |
265 | print "Checking changes submitted by %s ..." % author.email |
266 | updated_changes = [] |
267 | - for change in changes: |
268 | - gerrit_id = change['id']['id'] |
269 | - last_updated_on = datetime.strptime( |
270 | - change['lastUpdatedOn'], '%Y-%m-%d %H:%M:%S.%f000') |
271 | + for result in results: |
272 | + gerrit_id = result.get(GERRIT_NUMBER_KEY) |
273 | + last_updated_on = datetime.strptime(result.get(GERRIT_UPDATED_KEY), |
274 | + '%Y-%m-%d %H:%M:%S.%f000') |
275 | try: |
276 | existing = GerritChange.objects.get(gerrit_id=gerrit_id) |
277 | except GerritChange.DoesNotExist: |
278 | existing = None |
279 | - # Must get the change's details because we need their creation date. |
280 | - details = get_aosp_change_details(gerrit_id) |
281 | - date_created = datetime.strptime( |
282 | - details['createdOn'], '%Y-%m-%d %H:%M:%S.%f000') |
283 | + date_created = datetime.strptime(result.get(GERRIT_CREATED_KEY), |
284 | + '%Y-%m-%d %H:%M:%S.%f000') |
285 | change_dict = dict( |
286 | - id=gerrit_id, project=change['project']['key']['name'], |
287 | - last_updated_on=last_updated_on, status=change['status'], |
288 | - subject=change['subject'], date_created=date_created, |
289 | - change_id=change['key']['id']) |
290 | + id=gerrit_id, project=result.get(GERRIT_PROJECT_KEY), |
291 | + last_updated_on=last_updated_on, |
292 | + status=result.get(GERRIT_STATUS_KEY), |
293 | + subject=result.get(GERRIT_SUBJECT_KEY), date_created=date_created, |
294 | + change_id=result.get(GERRIT_ID_KEY)) |
295 | if existing is None: |
296 | create_patch_from_aosp_change(change_dict, author) |
297 | updated_changes.append(gerrit_id) |
298 | |
299 | === added file 'apps/patchmetrics/gerrit_json.py' |
300 | --- apps/patchmetrics/gerrit_json.py 1970-01-01 00:00:00 +0000 |
301 | +++ apps/patchmetrics/gerrit_json.py 2012-06-25 12:32:18 +0000 |
302 | @@ -0,0 +1,20 @@ |
303 | +# File with all the gerrit JSON valid keys. |
304 | +# For more information about the values defined here, look: |
305 | +# https://gerrit-review.googlesource.com/Documentation/json.html |
306 | +# Also, on bug https://bugs.launchpad.net/linaro-patchmetrics/+bug/1013577, |
307 | +# there is a sample JSON output from Gerrit new REST API. |
308 | +GERRIT_CREATED_KEY = 'created' |
309 | +GERRIT_ID_KEY = 'id' |
310 | +GERRIT_MORE_CHANGES = '_more_changes' |
311 | +GERRIT_NUMBER_KEY = '_number' |
312 | +GERRIT_OWNER_KEY = 'owner' |
313 | +GERRIT_OWNER_NAME_KEY = 'name' |
314 | +GERRIT_SORT_KEY = '_sortkey' |
315 | +GERRIT_UPDATED_KEY = 'updated' |
316 | +GERRIT_STATUS_KEY = 'status' |
317 | +GERRIT_PROJECT_KEY = 'project' |
318 | +GERRIT_SUBJECT_KEY = 'subject' |
319 | +# Gerrit JSON output starts with a magic prefix, making it not valid JSON. |
320 | +# For more info: |
321 | +# https://gerrit-review.googlesource.com/Documentation/rest-api.html |
322 | +GERRIT_SANITIZABLE_VALUE = ")]}'" |
323 | |
324 | === modified file 'apps/patchmetrics/tests/test_gerrit.py' |
325 | --- apps/patchmetrics/tests/test_gerrit.py 2011-07-19 11:56:25 +0000 |
326 | +++ apps/patchmetrics/tests/test_gerrit.py 2012-06-25 12:32:18 +0000 |
327 | @@ -4,10 +4,16 @@ |
328 | from django.test import TestCase |
329 | |
330 | from patchmetrics.gerrit import ( |
331 | + CHANGES_QUERY_PATH, |
332 | + QUERY_STRING_START, |
333 | + ROOT_REST_URL, |
334 | + create_gerrit_rest_query, |
335 | create_patch_from_aosp_change, |
336 | + create_request_query_url, |
337 | get_project_for_aosp_name, |
338 | get_project_name_for_aosp_name, |
339 | - update_local_gerrit_change_from_aosp_change) |
340 | + update_local_gerrit_change_from_aosp_change, |
341 | + ) |
342 | from patchmetrics.tests.factory import factory |
343 | |
344 | |
345 | @@ -50,7 +56,6 @@ |
346 | # date_last_state_change set to last_updated_on. |
347 | change_dict = copy(self.change_dict) |
348 | change_dict['status'] = 'MERGED' |
349 | - author = factory.makePerson() |
350 | patch = create_patch_from_aosp_change( |
351 | change_dict, factory.makePerson()) |
352 | self.assertEqual('Accepted', patch.state.name) |
353 | @@ -67,3 +72,41 @@ |
354 | self.assertEqual('MERGED', gerrit_change.status) |
355 | self.assertEqual('Accepted', gerrit_change.patch.state.name) |
356 | self.assertEqual(change_dict['subject'], gerrit_change.patch.name) |
357 | + |
358 | + def test_create_gerrit_rest_query(self): |
359 | + """Tests the creation of a valid gerrit query.""" |
360 | + query_params = {'key1': 'value1', 'key2': 'value2'} |
361 | + self.assertEqual('&q=key2:value2+key1:value1', |
362 | + create_gerrit_rest_query(query_params)) |
363 | + |
364 | + def test_create_gerrit_rest_query_empty(self): |
365 | + """Tests gerrit query creation with empty dictionary.""" |
366 | + self.assertEqual('', create_gerrit_rest_query({})) |
367 | + |
368 | + def test_create_gerrit_rest_query_with_none(self): |
369 | + """Tests that if a value is None it is not added.""" |
370 | + query_params = {'key1': 'value1', 'key2': None} |
371 | + self.assertEqual('&q=key1:value1', |
372 | + create_gerrit_rest_query(query_params)) |
373 | + |
374 | + def test_create_gerrit_request_query_with_list(self): |
375 | + """Tests that the function accepts only a dictionary, passing a list. |
376 | + """ |
377 | + self.assertRaises(TypeError, create_gerrit_rest_query, []) |
378 | + |
379 | + def test_create_gerrit_request_query_with_tuple(self): |
380 | + """Tests that the function accepts only a dictionary, passing a tuple. |
381 | + """ |
382 | + self.assertRaises(TypeError, create_gerrit_rest_query, ()) |
383 | + |
384 | + def test_create_request_query_url(self): |
385 | + """Tests the creation of a valid request URL.""" |
386 | + params = {'format': 'JSON', 'n': 100} |
387 | + query_params = {'owner': 'person@example.org', 'sortkey_after': 'abcd'} |
388 | + expected_result = ROOT_REST_URL + CHANGES_QUERY_PATH + \ |
389 | + QUERY_STRING_START + \ |
390 | + "n=100&format=JSON&q=owner:person@example.org+sortkey_after:abcd" |
391 | + self.assertEquals(expected_result, |
392 | + create_request_query_url(ROOT_REST_URL, |
393 | + CHANGES_QUERY_PATH, |
394 | + params, query_params)) |
The changes looks good. One typo needs to be fixed
"more complex queries need to be though of, since" , should be "more complex queries need to be thought of, since"
Also, I was not able to find any information on how to run the tests.
Can you point me where this information is present OR If it does not exist then could you add a README somewhere ?
You can make these changes while merging. I will approve this so that we can go ahead with trying to see if it fixes our initial problems.