]>
Commit | Line | Data |
---|---|---|
1d09f67e TL |
1 | #!/usr/bin/env python |
2 | ||
3 | # | |
4 | # Licensed to the Apache Software Foundation (ASF) under one or more | |
5 | # contributor license agreements. See the NOTICE file distributed with | |
6 | # this work for additional information regarding copyright ownership. | |
7 | # The ASF licenses this file to You under the Apache License, Version 2.0 | |
8 | # (the "License"); you may not use this file except in compliance with | |
9 | # the License. You may obtain a copy of the License at | |
10 | # | |
11 | # http://www.apache.org/licenses/LICENSE-2.0 | |
12 | # | |
13 | # Unless required by applicable law or agreed to in writing, software | |
14 | # distributed under the License is distributed on an "AS IS" BASIS, | |
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | # See the License for the specific language governing permissions and | |
17 | # limitations under the License. | |
18 | # | |
19 | ||
20 | from collections import namedtuple | |
21 | ||
22 | import pytest | |
23 | ||
24 | import merge_arrow_pr | |
25 | ||
26 | ||
27 | FakeIssue = namedtuple('issue', ['fields']) | |
28 | FakeFields = namedtuple('fields', ['status', 'summary', 'assignee', | |
29 | 'components', 'fixVersions']) | |
30 | FakeAssignee = namedtuple('assignee', ['displayName']) | |
31 | FakeStatus = namedtuple('status', ['name']) | |
32 | FakeComponent = namedtuple('component', ['name']) | |
33 | FakeVersion = namedtuple('version', ['name', 'raw']) | |
34 | ||
35 | RAW_VERSION_JSON = [ | |
36 | {'name': 'JS-0.4.0', 'released': False}, | |
37 | {'name': '0.11.0', 'released': False}, | |
38 | {'name': '0.12.0', 'released': False}, | |
39 | {'name': '0.10.0', 'released': True}, | |
40 | {'name': '0.9.0', 'released': True} | |
41 | ] | |
42 | ||
43 | ||
44 | SOURCE_VERSIONS = [FakeVersion(raw['name'], raw) | |
45 | for raw in RAW_VERSION_JSON] | |
46 | ||
47 | TRANSITIONS = [{'name': 'Resolve Issue', 'id': 1}] | |
48 | ||
49 | jira_id = 'ARROW-1234' | |
50 | status = FakeStatus('In Progress') | |
51 | fields = FakeFields(status, 'issue summary', FakeAssignee('groundhog'), | |
52 | [FakeComponent('C++'), FakeComponent('Format')], | |
53 | []) | |
54 | FAKE_ISSUE_1 = FakeIssue(fields) | |
55 | ||
56 | ||
57 | class FakeJIRA: | |
58 | ||
59 | def __init__(self, issue=None, project_versions=None, transitions=None, | |
60 | current_fix_versions=None): | |
61 | self._issue = issue | |
62 | self._project_versions = project_versions | |
63 | self._transitions = transitions | |
64 | ||
65 | def issue(self, jira_id): | |
66 | return self._issue | |
67 | ||
68 | def transitions(self, jira_id): | |
69 | return self._transitions | |
70 | ||
71 | def transition_issue(self, jira_id, transition_id, comment=None, | |
72 | fixVersions=None): | |
73 | self.captured_transition = { | |
74 | 'jira_id': jira_id, | |
75 | 'transition_id': transition_id, | |
76 | 'comment': comment, | |
77 | 'fixVersions': fixVersions | |
78 | } | |
79 | ||
80 | def get_candidate_fix_versions(self): | |
81 | return SOURCE_VERSIONS, ['0.12.0'] | |
82 | ||
83 | def project_versions(self, project): | |
84 | return self._project_versions | |
85 | ||
86 | ||
87 | class FakeCLI: | |
88 | ||
89 | def __init__(self, responses=()): | |
90 | self.responses = responses | |
91 | self.position = 0 | |
92 | ||
93 | def prompt(self, prompt): | |
94 | response = self.responses[self.position] | |
95 | self.position += 1 | |
96 | return response | |
97 | ||
98 | def fail(self, msg): | |
99 | raise Exception(msg) | |
100 | ||
101 | ||
102 | def test_jira_fix_versions(): | |
103 | jira = FakeJIRA(project_versions=SOURCE_VERSIONS, | |
104 | transitions=TRANSITIONS) | |
105 | ||
106 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
107 | all_versions, default_versions = issue.get_candidate_fix_versions() | |
108 | assert all_versions == SOURCE_VERSIONS | |
109 | assert default_versions == ['0.11.0'] | |
110 | ||
111 | ||
112 | def test_jira_no_suggest_patch_release(): | |
113 | versions_json = [ | |
114 | {'name': '0.11.1', 'released': False}, | |
115 | {'name': '0.12.0', 'released': False}, | |
116 | ] | |
117 | ||
118 | versions = [FakeVersion(raw['name'], raw) for raw in versions_json] | |
119 | ||
120 | jira = FakeJIRA(project_versions=versions, transitions=TRANSITIONS) | |
121 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
122 | all_versions, default_versions = issue.get_candidate_fix_versions() | |
123 | assert all_versions == versions | |
124 | assert default_versions == ['0.12.0'] | |
125 | ||
126 | ||
127 | def test_jira_parquet_no_suggest_non_cpp(): | |
128 | # ARROW-7351 | |
129 | versions_json = [ | |
130 | {'name': 'cpp-1.5.0', 'released': True}, | |
131 | {'name': 'cpp-1.6.0', 'released': False}, | |
132 | {'name': 'cpp-1.7.0', 'released': False}, | |
133 | {'name': '1.11.0', 'released': False}, | |
134 | {'name': '1.12.0', 'released': False} | |
135 | ] | |
136 | ||
137 | versions = [FakeVersion(raw['name'], raw) | |
138 | for raw in versions_json] | |
139 | ||
140 | jira = FakeJIRA(project_versions=versions, transitions=TRANSITIONS) | |
141 | issue = merge_arrow_pr.JiraIssue(jira, 'PARQUET-1713', 'PARQUET', | |
142 | FakeCLI()) | |
143 | all_versions, default_versions = issue.get_candidate_fix_versions() | |
144 | assert all_versions == versions | |
145 | assert default_versions == ['cpp-1.6.0'] | |
146 | ||
147 | ||
148 | def test_jira_invalid_issue(): | |
149 | class Mock: | |
150 | ||
151 | def issue(self, jira_id): | |
152 | raise Exception("not found") | |
153 | ||
154 | with pytest.raises(Exception): | |
155 | merge_arrow_pr.JiraIssue(Mock(), 'ARROW-1234', 'ARROW', FakeCLI()) | |
156 | ||
157 | ||
158 | def test_jira_resolve(): | |
159 | jira = FakeJIRA(issue=FAKE_ISSUE_1, | |
160 | project_versions=SOURCE_VERSIONS, | |
161 | transitions=TRANSITIONS) | |
162 | ||
163 | my_comment = 'my comment' | |
164 | fix_versions = [SOURCE_VERSIONS[1].raw] | |
165 | ||
166 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
167 | issue.resolve(fix_versions, my_comment) | |
168 | ||
169 | assert jira.captured_transition == { | |
170 | 'jira_id': 'ARROW-1234', | |
171 | 'transition_id': 1, | |
172 | 'comment': my_comment, | |
173 | 'fixVersions': fix_versions | |
174 | } | |
175 | ||
176 | ||
177 | def test_jira_resolve_non_mainline(): | |
178 | jira = FakeJIRA(issue=FAKE_ISSUE_1, | |
179 | project_versions=SOURCE_VERSIONS, | |
180 | transitions=TRANSITIONS) | |
181 | ||
182 | my_comment = 'my comment' | |
183 | fix_versions = [SOURCE_VERSIONS[0].raw] | |
184 | ||
185 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
186 | issue.resolve(fix_versions, my_comment) | |
187 | ||
188 | assert jira.captured_transition == { | |
189 | 'jira_id': 'ARROW-1234', | |
190 | 'transition_id': 1, | |
191 | 'comment': my_comment, | |
192 | 'fixVersions': fix_versions | |
193 | } | |
194 | ||
195 | ||
196 | def test_jira_resolve_released_fix_version(): | |
197 | # ARROW-5083 | |
198 | jira = FakeJIRA(issue=FAKE_ISSUE_1, | |
199 | project_versions=SOURCE_VERSIONS, | |
200 | transitions=TRANSITIONS) | |
201 | ||
202 | cmd = FakeCLI(responses=['0.9.0']) | |
203 | fix_versions_json = merge_arrow_pr.prompt_for_fix_version(cmd, jira) | |
204 | assert fix_versions_json == [RAW_VERSION_JSON[-1]] | |
205 | ||
206 | ||
207 | def test_multiple_authors_bad_input(): | |
208 | a0 = 'Jimbob Crawfish <jimbob.crawfish@gmail.com>' | |
209 | a1 = 'Jarvis McCratchett <jarvis.mccratchett@hotmail.com>' | |
210 | a2 = 'Hank Miller <hank.miller@protonmail.com>' | |
211 | distinct_authors = [a0, a1] | |
212 | ||
213 | cmd = FakeCLI(responses=['']) | |
214 | primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( | |
215 | cmd, distinct_authors) | |
216 | assert primary_author == a0 | |
217 | assert new_distinct_authors == [a0, a1] | |
218 | ||
219 | cmd = FakeCLI(responses=['oops', a1]) | |
220 | primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( | |
221 | cmd, distinct_authors) | |
222 | assert primary_author == a1 | |
223 | assert new_distinct_authors == [a1, a0] | |
224 | ||
225 | cmd = FakeCLI(responses=[a2]) | |
226 | primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( | |
227 | cmd, distinct_authors) | |
228 | assert primary_author == a2 | |
229 | assert new_distinct_authors == [a2, a0, a1] | |
230 | ||
231 | ||
232 | def test_jira_already_resolved(): | |
233 | status = FakeStatus('Resolved') | |
234 | fields = FakeFields(status, 'issue summary', FakeAssignee('groundhog'), | |
235 | [FakeComponent('Java')], []) | |
236 | issue = FakeIssue(fields) | |
237 | ||
238 | jira = FakeJIRA(issue=issue, | |
239 | project_versions=SOURCE_VERSIONS, | |
240 | transitions=TRANSITIONS) | |
241 | ||
242 | fix_versions = [SOURCE_VERSIONS[0].raw] | |
243 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
244 | ||
245 | with pytest.raises(Exception, | |
246 | match="ARROW-1234 already has status 'Resolved'"): | |
247 | issue.resolve(fix_versions, "") | |
248 | ||
249 | ||
250 | def test_no_unset_point_release_fix_version(): | |
251 | # ARROW-6915: We have had the problem of issues marked with a point release | |
252 | # having their fix versions overwritten by the merge tool. This verifies | |
253 | # that existing patch release versions are carried over | |
254 | status = FakeStatus('In Progress') | |
255 | ||
256 | versions_json = { | |
257 | '0.14.2': {'name': '0.14.2', 'id': 1}, | |
258 | '0.15.1': {'name': '0.15.1', 'id': 2}, | |
259 | '0.16.0': {'name': '0.16.0', 'id': 3}, | |
260 | '0.17.0': {'name': '0.17.0', 'id': 4} | |
261 | } | |
262 | ||
263 | fields = FakeFields(status, 'summary', FakeAssignee('someone'), | |
264 | [FakeComponent('Java')], | |
265 | [FakeVersion(v, versions_json[v]) | |
266 | for v in ['0.17.0', '0.15.1', '0.14.2']]) | |
267 | issue = FakeIssue(fields) | |
268 | ||
269 | jira = FakeJIRA(issue=issue, project_versions=SOURCE_VERSIONS, | |
270 | transitions=TRANSITIONS) | |
271 | ||
272 | issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) | |
273 | issue.resolve([versions_json['0.16.0']], "a comment") | |
274 | ||
275 | assert jira.captured_transition == { | |
276 | 'jira_id': 'ARROW-1234', | |
277 | 'transition_id': 1, | |
278 | 'comment': 'a comment', | |
279 | 'fixVersions': [versions_json[v] | |
280 | for v in ['0.16.0', '0.15.1', '0.14.2']] | |
281 | } | |
282 | ||
283 | issue.resolve([versions_json['0.15.1']], "a comment") | |
284 | ||
285 | assert jira.captured_transition == { | |
286 | 'jira_id': 'ARROW-1234', | |
287 | 'transition_id': 1, | |
288 | 'comment': 'a comment', | |
289 | 'fixVersions': [versions_json[v] for v in ['0.15.1', '0.14.2']] | |
290 | } | |
291 | ||
292 | ||
293 | def test_jira_output_no_components(): | |
294 | # ARROW-5472 | |
295 | status = 'Interesting work' | |
296 | components = [] | |
297 | output = merge_arrow_pr.format_jira_output( | |
298 | 'ARROW-1234', 'Resolved', status, FakeAssignee('Foo Bar'), | |
299 | components) | |
300 | ||
301 | assert output == """=== JIRA ARROW-1234 === | |
302 | Summary\t\tInteresting work | |
303 | Assignee\tFoo Bar | |
304 | Components\tNO COMPONENTS!!! | |
305 | Status\t\tResolved | |
306 | URL\t\thttps://issues.apache.org/jira/browse/ARROW-1234""" | |
307 | ||
308 | output = merge_arrow_pr.format_jira_output( | |
309 | 'ARROW-1234', 'Resolved', status, FakeAssignee('Foo Bar'), | |
310 | [FakeComponent('C++'), FakeComponent('Python')]) | |
311 | ||
312 | assert output == """=== JIRA ARROW-1234 === | |
313 | Summary\t\tInteresting work | |
314 | Assignee\tFoo Bar | |
315 | Components\tC++, Python | |
316 | Status\t\tResolved | |
317 | URL\t\thttps://issues.apache.org/jira/browse/ARROW-1234""" |