#!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # from collections import namedtuple import pytest import merge_arrow_pr FakeIssue = namedtuple('issue', ['fields']) FakeFields = namedtuple('fields', ['status', 'summary', 'assignee', 'components', 'fixVersions']) FakeAssignee = namedtuple('assignee', ['displayName']) FakeStatus = namedtuple('status', ['name']) FakeComponent = namedtuple('component', ['name']) FakeVersion = namedtuple('version', ['name', 'raw']) RAW_VERSION_JSON = [ {'name': 'JS-0.4.0', 'released': False}, {'name': '0.11.0', 'released': False}, {'name': '0.12.0', 'released': False}, {'name': '0.10.0', 'released': True}, {'name': '0.9.0', 'released': True} ] SOURCE_VERSIONS = [FakeVersion(raw['name'], raw) for raw in RAW_VERSION_JSON] TRANSITIONS = [{'name': 'Resolve Issue', 'id': 1}] jira_id = 'ARROW-1234' status = FakeStatus('In Progress') fields = FakeFields(status, 'issue summary', FakeAssignee('groundhog'), [FakeComponent('C++'), FakeComponent('Format')], []) FAKE_ISSUE_1 = FakeIssue(fields) class FakeJIRA: def __init__(self, issue=None, project_versions=None, transitions=None, current_fix_versions=None): self._issue = issue self._project_versions = project_versions self._transitions = transitions def issue(self, jira_id): return self._issue def transitions(self, jira_id): return self._transitions def transition_issue(self, jira_id, transition_id, comment=None, fixVersions=None): self.captured_transition = { 'jira_id': jira_id, 'transition_id': transition_id, 'comment': comment, 'fixVersions': fixVersions } def get_candidate_fix_versions(self): return SOURCE_VERSIONS, ['0.12.0'] def project_versions(self, project): return self._project_versions class FakeCLI: def __init__(self, responses=()): self.responses = responses self.position = 0 def prompt(self, prompt): response = self.responses[self.position] self.position += 1 return response def fail(self, msg): raise Exception(msg) def test_jira_fix_versions(): jira = FakeJIRA(project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) all_versions, default_versions = issue.get_candidate_fix_versions() assert all_versions == SOURCE_VERSIONS assert default_versions == ['0.11.0'] def test_jira_no_suggest_patch_release(): versions_json = [ {'name': '0.11.1', 'released': False}, {'name': '0.12.0', 'released': False}, ] versions = [FakeVersion(raw['name'], raw) for raw in versions_json] jira = FakeJIRA(project_versions=versions, transitions=TRANSITIONS) issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) all_versions, default_versions = issue.get_candidate_fix_versions() assert all_versions == versions assert default_versions == ['0.12.0'] def test_jira_parquet_no_suggest_non_cpp(): # ARROW-7351 versions_json = [ {'name': 'cpp-1.5.0', 'released': True}, {'name': 'cpp-1.6.0', 'released': False}, {'name': 'cpp-1.7.0', 'released': False}, {'name': '1.11.0', 'released': False}, {'name': '1.12.0', 'released': False} ] versions = [FakeVersion(raw['name'], raw) for raw in versions_json] jira = FakeJIRA(project_versions=versions, transitions=TRANSITIONS) issue = merge_arrow_pr.JiraIssue(jira, 'PARQUET-1713', 'PARQUET', FakeCLI()) all_versions, default_versions = issue.get_candidate_fix_versions() assert all_versions == versions assert default_versions == ['cpp-1.6.0'] def test_jira_invalid_issue(): class Mock: def issue(self, jira_id): raise Exception("not found") with pytest.raises(Exception): merge_arrow_pr.JiraIssue(Mock(), 'ARROW-1234', 'ARROW', FakeCLI()) def test_jira_resolve(): jira = FakeJIRA(issue=FAKE_ISSUE_1, project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) my_comment = 'my comment' fix_versions = [SOURCE_VERSIONS[1].raw] issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) issue.resolve(fix_versions, my_comment) assert jira.captured_transition == { 'jira_id': 'ARROW-1234', 'transition_id': 1, 'comment': my_comment, 'fixVersions': fix_versions } def test_jira_resolve_non_mainline(): jira = FakeJIRA(issue=FAKE_ISSUE_1, project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) my_comment = 'my comment' fix_versions = [SOURCE_VERSIONS[0].raw] issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) issue.resolve(fix_versions, my_comment) assert jira.captured_transition == { 'jira_id': 'ARROW-1234', 'transition_id': 1, 'comment': my_comment, 'fixVersions': fix_versions } def test_jira_resolve_released_fix_version(): # ARROW-5083 jira = FakeJIRA(issue=FAKE_ISSUE_1, project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) cmd = FakeCLI(responses=['0.9.0']) fix_versions_json = merge_arrow_pr.prompt_for_fix_version(cmd, jira) assert fix_versions_json == [RAW_VERSION_JSON[-1]] def test_multiple_authors_bad_input(): a0 = 'Jimbob Crawfish ' a1 = 'Jarvis McCratchett ' a2 = 'Hank Miller ' distinct_authors = [a0, a1] cmd = FakeCLI(responses=['']) primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( cmd, distinct_authors) assert primary_author == a0 assert new_distinct_authors == [a0, a1] cmd = FakeCLI(responses=['oops', a1]) primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( cmd, distinct_authors) assert primary_author == a1 assert new_distinct_authors == [a1, a0] cmd = FakeCLI(responses=[a2]) primary_author, new_distinct_authors = merge_arrow_pr.get_primary_author( cmd, distinct_authors) assert primary_author == a2 assert new_distinct_authors == [a2, a0, a1] def test_jira_already_resolved(): status = FakeStatus('Resolved') fields = FakeFields(status, 'issue summary', FakeAssignee('groundhog'), [FakeComponent('Java')], []) issue = FakeIssue(fields) jira = FakeJIRA(issue=issue, project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) fix_versions = [SOURCE_VERSIONS[0].raw] issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) with pytest.raises(Exception, match="ARROW-1234 already has status 'Resolved'"): issue.resolve(fix_versions, "") def test_no_unset_point_release_fix_version(): # ARROW-6915: We have had the problem of issues marked with a point release # having their fix versions overwritten by the merge tool. This verifies # that existing patch release versions are carried over status = FakeStatus('In Progress') versions_json = { '0.14.2': {'name': '0.14.2', 'id': 1}, '0.15.1': {'name': '0.15.1', 'id': 2}, '0.16.0': {'name': '0.16.0', 'id': 3}, '0.17.0': {'name': '0.17.0', 'id': 4} } fields = FakeFields(status, 'summary', FakeAssignee('someone'), [FakeComponent('Java')], [FakeVersion(v, versions_json[v]) for v in ['0.17.0', '0.15.1', '0.14.2']]) issue = FakeIssue(fields) jira = FakeJIRA(issue=issue, project_versions=SOURCE_VERSIONS, transitions=TRANSITIONS) issue = merge_arrow_pr.JiraIssue(jira, 'ARROW-1234', 'ARROW', FakeCLI()) issue.resolve([versions_json['0.16.0']], "a comment") assert jira.captured_transition == { 'jira_id': 'ARROW-1234', 'transition_id': 1, 'comment': 'a comment', 'fixVersions': [versions_json[v] for v in ['0.16.0', '0.15.1', '0.14.2']] } issue.resolve([versions_json['0.15.1']], "a comment") assert jira.captured_transition == { 'jira_id': 'ARROW-1234', 'transition_id': 1, 'comment': 'a comment', 'fixVersions': [versions_json[v] for v in ['0.15.1', '0.14.2']] } def test_jira_output_no_components(): # ARROW-5472 status = 'Interesting work' components = [] output = merge_arrow_pr.format_jira_output( 'ARROW-1234', 'Resolved', status, FakeAssignee('Foo Bar'), components) assert output == """=== JIRA ARROW-1234 === Summary\t\tInteresting work Assignee\tFoo Bar Components\tNO COMPONENTS!!! Status\t\tResolved URL\t\thttps://issues.apache.org/jira/browse/ARROW-1234""" output = merge_arrow_pr.format_jira_output( 'ARROW-1234', 'Resolved', status, FakeAssignee('Foo Bar'), [FakeComponent('C++'), FakeComponent('Python')]) assert output == """=== JIRA ARROW-1234 === Summary\t\tInteresting work Assignee\tFoo Bar Components\tC++, Python Status\t\tResolved URL\t\thttps://issues.apache.org/jira/browse/ARROW-1234"""