NOTICK - Python script for creating test tickets in JIRA (#3979)

Python script for creating 'Platform Test' and 'Platform Test Run'
instances in JIRA for use in release testing.
This commit is contained in:
Tommy Lillehagen 2018-10-02 17:55:22 +01:00 committed by GitHub
parent bc6ef74c6a
commit fa8761793f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 717 additions and 0 deletions

1
release-tools/testing/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pyc

View File

@ -0,0 +1,83 @@
# Release Tools - Test Tracker and Generator
## Introduction
This command-line tool lets the user create and track tests in the [R3T](https://r3-cev.atlassian.net/projects/R3T) JIRA project. All generic test cases are captured as tickets of type **Platform Test Template** with a label "OS" for tests pertaining to **Corda Open Source**, "ENT" for **Corda Enterprise**, and "NS" for **Corda Network Services**. These tickets can be set to **Active** or **Inactive** status based on their relevance for a particular release.
The tool creates a set of new release tests by cloning the current set of active test templates into a set of **Platform Test** tickets. These will each get assigned to the appropriate target version. Further, the tool lets the user create sub-tasks for each of the release tests, one for each release candidate. These steps are described in more detail further down.
## List Test Cases
To list the active test cases for a product, run the following command:
```bash
$ ./test-manager list-tests <PRODUCT>
```
Where `<PRODUCT>` is either `OS`, `ENT` or `NS`. This will list the test cases that are currently applicable to Corda Open Source, Corda Enterprise and Corda Network Services, respectively.
## Show Test Status
To show the status of all test runs for a specific release or release candidate, run:
```bash
$ ./test-manager status <PRODUCT> <VERSION> <CANDIDATE>
```
Here, `<VERSION>` represents the target version, e.g., product `OS` and version `3.3` would represent Corda Open Source 3.3. `<CANDIDATE>` is optional and will narrow down the report to only show the provided candidate version, e.g., `1` for `RC01`.
## Create JIRA Version
To create a new release version in JIRA, you can run the following command:
```bash
$ ./test-manager create-version <PRODUCT> <VERSION> <CANDIDATE>
```
Note that `<CANDIDATE>` is optional. This command will create new versions in the following JIRA projects: `CORDA`, `ENT`, `ENM`, `CID` and `R3T`.
## Create Release Tests
To create the set of parent tests for a new release, you can run:
```bash
$ ./test-manager create-release-tests <PRODUCT> <VERSION>
```
This will create the test cases, but none of the test run tickets for respective release candidates. Note also that "blocks"-links between active test templates will be carried across to the created test tickets.
## Create Release Candidate Tests
To create a set of test run tickets for a new release candidate, you can run:
```bash
$ ./test-manager create-release-candidate-tests <PRODUCT> <VERSION> <CANDIDATE>
```
This will create a new sub-task under each of the test tickets for `<PRODUCT>` `<VERSION>`, for release candidate `<CANDIDATE>`.
## Options
Each command described above has a set of additional options. More specifically, if you want to use a particular JIRA user instead of being prompted for a user name every time, you can specify `--user <USER>`. For verbose logging, you can supply `--verbose` or `-v`. And to auto-reply to the prompt of whether to proceed or not, provide `--yes` or `-y`.
There is also a useful dry-run option, `--dry-run` or `-d`, that lets you run through the command without creating any tickets or applying any changes to JIRA.
## Example
As an example, say you want to create test cases for Corda Network Services 1.0 RC01. You would then follow the following steps:
```bash
$ ./test-manager create-version NS 1.0 # Create "Corda Network Services 1.0" - if it doesn't exist
$ ./test-manager create-version NS 1.0 1 # Create "Corda Network Services 1.0 RC01" - if it doesn't exist
$ ./test-manager create-release-tests NS 1.0 # Create test cases
$ ./test-manager create-release-candidate-tests NS 1.0 1 # Create test run for release candidate
```
Later, when it's time to test RC02, you simply run the following:
```bash
$ ./test-manager create-version NS 1.0 2
$ ./test-manager create-release-candidate-tests NS 1.0 2
```
That's it. Voila, you've got yourself a whole new set of JIRA tickets :-)

View File

@ -0,0 +1,71 @@
from __future__ import print_function
from argparse import Action, ArgumentParser
import sys, traceback
# {{{ Representation of a command-line program
class Program:
# Create a new command-line program represenation, provided an optional name and description
def __init__(self, name=None, description=None):
self.parser = ArgumentParser(name, description=description)
self.subparsers = self.parser.add_subparsers(title='commands')
self.arguments = []
# Enter program definition block
def __enter__(self):
return self
# Add argument to the top-level command-line interface and all registered sub-commands
def add(self, name, *args, **kwargs):
self.parser.add_argument(name, *args, **kwargs)
self.arguments.append(([name] + list(args), kwargs))
# Add sub-command to the set of command-line options
def command(self, name, description, handler):
return Command(self, self.subparsers, name, description, handler)
# Parse arguments from the command line, and run the associated command handler
def __exit__(self, type, value, tb):
args = self.parser.parse_args()
try:
if 'func' in args:
args.func(args)
else:
self.parser.print_help()
except KeyboardInterrupt:
print()
except Exception as error:
if args.verbose:
t, exception, tb = sys.exc_info()
self.parser.error('{}\n\n{}'.format(error.message, '\n'.join(traceback.format_tb(tb))))
else:
self.parser.error(error.message)
# }}}
# {{{ Representation of a sub-command of a command-line program
class Command:
# Create a sub-command, provided a name, description and command handler
def __init__(self, program, subparsers, name, description, handler):
self.program = program
self.subparsers = subparsers
self.name = name
self.description = description
self.handler = handler
# Enter sub-command definition block
def __enter__(self):
self.parser = self.subparsers.add_parser(self.name, description=self.description)
return self
# Add argument to the CLI command
def add(self, name, *args, **kwargs):
self.parser.add_argument(name, *args, **kwargs)
# Exit sub-command definition block and register default handler
def __exit__(self, type, value, traceback):
for (args, kwargs) in self.program.arguments:
self.parser.add_argument(*args, **kwargs)
self.parser.set_defaults(func=self.handler)
# }}}

View File

@ -0,0 +1,187 @@
# {{{ JIRA dependency
from jira import JIRA
from jira.exceptions import JIRAError
# }}}
# {{{ Class for interacting with a hosted JIRA system
class Jira:
# {{{ Constants
BLOCKS = 'Blocks'
DUPLICATE = 'Duplicate'
RELATES = 'Relates'
# }}}
# {{{ init(address) - Initialise JIRA class, pointing it to the JIRA endpoint
def __init__(self, address='https://r3-cev.atlassian.net'):
self.address = address
self.jira = None
self.mock_key = 1
self.custom_fields_by_name, self.custom_fields_by_key = {}, {}
# }}}
# {{{ login(user, password) - Log in as a specific JIRA user
def login(self, user, password):
try:
self.jira = JIRA(self.address, auth=(user, password))
for x in self.jira.fields():
if x['custom']:
self.custom_fields_by_name[x['name']] = x['key']
self.custom_fields_by_key[x['key']] = x['name']
return self
except Exception as error:
message = error.message
if isinstance(error, JIRAError):
message = error.text if error.text and len(error.text) > 0 and not error.text.startswith('<!') else message
raise Exception('failed to log in to JIRA{}{}'.format(': ' if message else '', message))
# }}}
# {{{ search(query) - Search for issues and manually traverse pages if multiple pages are returned
def search(self, query, *args):
max_count = 50
index, offset, count = 0, 0, max_count
query = query.format(*args) if len(args) > 0 else query
while count == max_count:
try:
issues = self.jira.search_issues(query, maxResults=max_count, startAt=offset)
count = len(issues)
offset += count
for issue in issues:
index += 1
yield Issue(self, index=index, issue=issue)
except JIRAError as error:
raise Exception('failed to run query "{}": {}'.format(query, error.text))
# }}}
# {{{ find(key) - Look up issue by key
def find(self, key):
try:
issue = self.jira.issue(key)
return Issue(self, issue=issue)
except JIRAError as error:
raise Exception('failed to look up issue "{}": {}'.format(key, error.text))
# }}}
# {{{ create(fields, dry_run) - Create a new issue
def create(self, fields, dry_run=False):
if dry_run:
return Issue(self, fields=fields)
try:
issue = self.jira.create_issue(fields)
return Issue(self, issue=issue)
except JIRAError as error:
raise Exception('failed to create issue: {}'.format(error.text))
# }}}
# {{{ link(issue_key, other_issue_key, relationship, dry_run) - Link one issue to another
def link(self, issue_key, other_issue_key, relationship=RELATES, dry_run=False):
if dry_run:
return
try:
self.jira.create_issue_link(
type=relationship,
inwardIssue=issue_key,
outwardIssue=other_issue_key,
comment={
'body': 'Linked {} to {}'.format(issue_key, other_issue_key),
}
)
except JIRAError as error:
raise Exception('failed to link {} and {}: {}'.format(issue_key, other_issue_key, error.text))
# }}}
# }}}
# {{{ Representation of a JIRA issue
class Issue:
mock_index = 1
# {{{ init(jira, index, issue, key, fields) - Instantiate an abstract representation of an issue
def __init__(self, jira, index=0, issue=None, key=None, fields=None):
self._jira = jira
self._index = index
self._issue = issue
self._fields = fields
self._key = key if key else u'DRY-{:03d}'.format(Issue.mock_index)
if not key and not issue:
Issue.mock_index += 1
# }}}
# {{{ getattr(key) - Get attribute from issue
def __getattr__(self, key):
if key == 'index':
return self._index
if self._issue:
value = self._issue.__getattr__(key)
return WrappedDictionary(value) if key == 'fields' else value
elif self._fields:
if key == 'key':
return self._key
elif key == 'fields':
return WrappedDictionary(self._fields)
return None
# }}}
# {{{ getitem(key) - Get item from issue
def __getitem__(self, key):
return self.__getattr__(key)
# }}}
# {{{ str() - Get a string representation of the issue
def __str__(self):
summary = self.fields.summary.strip()
labels = self.fields.labels
if len(labels) > 0:
return u'[{}] {} ({})'.format(self.key, summary, ', '.join(labels))
else:
return u'[{}] {}'.format(self.key, summary)
# }}}
# {{{ clone(..., dry_run) - Create a clone of the issue, resetting any provided fields)
def clone(self, **kwargs):
dry_run = kwargs['dry_run'] if 'dry_run' in kwargs else False
fields = self.fields.to_dict()
whitelisted_fields = [
'project', 'summary', 'description', 'issuetype', 'labels', 'parent', 'priority',
self._jira.custom_fields_by_name['Target Version/s'],
]
if 'parent' not in kwargs:
whitelisted_fields += [
self._jira.custom_fields_by_name['Epic Link'],
self._jira.custom_fields_by_name['Preconditions'],
self._jira.custom_fields_by_name['Test Steps'],
self._jira.custom_fields_by_name['Acceptance Criteria'],
self._jira.custom_fields_by_name['Primary Test Environment'],
]
for key in kwargs:
value = kwargs[key]
if key == 'parent' and type(value) in (str, unicode):
fields[key] = { 'key' : value }
elif key == 'version':
fields[self._jira.custom_fields_by_name['Target Version/s']] = [{ 'name' : value }]
else:
fields[key] = value
for key in [key for key in fields if key not in whitelisted_fields]:
del fields[key]
new_issue = self._jira.create(fields, dry_run=dry_run)
return new_issue
# }}}
# }}}
# {{{ Dictionary with attribute getters
class WrappedDictionary:
def __init__(self, dictionary):
self.dictionary = dictionary
def __getattr__(self, key):
return self.__getitem__(key)
def __getitem__(self, key):
return self.dictionary[key]
def to_dict(self):
return dict(self.dictionary)
# }}}

View File

@ -0,0 +1,60 @@
# {{{ Dependencies
from __future__ import print_function
import sys
try:
from getpass import getpass
except:
def getpass(message): return raw_input(message)
try:
from keyring import get_password, set_password
except:
def get_password(account, user): return None
def set_password(account, user, password): pass
# Python 2.x fix; raw_input was renamed to input in Python 3
try: input = raw_input
except NameError: pass
# }}}
# {{{ prompt(message, secret) - Get input from user; if secret is true, hide the input
def prompt(message, secret=False):
try:
return getpass(message) if secret else input(message)
except:
print()
return ''
# }}}
# {{{ confirm(message, auto_yes) - Request confirmation from user and proceed if the response is 'yes'
def confirm(message, auto_yes=False):
if auto_yes:
print(message.replace('?', '.'))
return
if not prompt(u'{} (y/N) '.format(message)).lower().strip().startswith('y'):
sys.exit(1)
# }}}
# {{{ login(account, user, password, use_keyring) - Present user with login prompt and return the provided username and password. If use_keyring is true, use previously provided password (if any)
def login(account, user=None, password=None, use_keyring=True):
if not user:
user = prompt('Username: ')
user = u'{}@r3.com'.format(user) if '@' not in user else user
if not user: return (None, None)
else:
user = u'{}@r3.com'.format(user) if '@' not in user else user
print('Username: {}'.format(user))
password = get_password(account, user) if password is None and use_keyring else password
if not password:
password = prompt('Password: ', secret=True)
if not password: return (None, None)
else:
print('Password: ********')
if use_keyring:
set_password(account, user, password)
print()
return (user, password)
# }}}

View File

@ -0,0 +1,3 @@
jira==2.0.0
keyring==13.1.0
termcolor==1.1.0

View File

@ -0,0 +1,312 @@
#!/usr/bin/env python
# {{{ Dependencies
from __future__ import print_function
import sys
from args import Program
from login_manager import login, confirm
from jira_manager import Jira
# }}}
# {{{ Dependencies for printing coloured content to the console
try:
from termcolor import colored
def blue(message): return colored(message, 'blue')
def green(message): return colored(message, 'green')
def red(message): return colored(message, 'red')
def yellow(message): return colored(message, 'yellow')
def faint(message): return colored(message, 'white', attrs=['dark'])
def on_green(message): return colored(message, 'white', 'on_green')
def on_red(message): return colored(message, 'white', 'on_red')
def blue_on_white(message): return colored(message, 'blue', 'on_white')
def yellow_on_white(message): return colored(message, 'yellow', 'on_white')
except:
def blue(message): return u'[{}]'.format(message)
def green(message): return message
def red(message): return message
def yellow(message): return message
def faint(message): return message
def on_green(message): return message
def on_red(message): return message
def blue_on_white(message): return message
def yellow_on_white(message): return message
# }}}
# {{{ Mapping from product code to product name
product_map = {
'OS' : 'Corda',
'ENT' : 'Corda Enterprise',
'NS' : 'Corda Network Services',
'TEST' : 'Corda', # for demo and test purposes
}
# }}}
# {{{ JIRA queries
QUERY_LIST_TEST_CASES = \
u'project = R3T AND type = "Platform Test Template" AND status = Active AND labels = "{}" ORDER BY key'
QUERY_LIST_TEST_INSTANCES = \
u'project = R3T AND type = "Platform Test" AND labels = "{}" AND "Target Version/s" = "{}" ORDER BY key'
QUERY_LIST_TEST_INSTANCE_FOR_TICKET = \
u'project = R3T AND type = "Platform Test" AND labels = "{}" AND "Target Version/s" = "{}" AND issue IN linkedIssues({})'
QUERY_LIST_ALL_TEST_RUNS_FOR_TICKET = \
u'project = R3T AND type = "Platform Test Run" AND labels = "{}" AND parent = {} ORDER BY "Target Version/S"'
QUERY_LIST_TEST_RUN_FOR_TICKET = \
u'project = R3T AND type = "Platform Test Run" AND labels = "{}" AND "Target Version/s" = "{}" AND parent = {}'
QUERY_LIST_BLOCKING_TEST_CASES = \
u'project = R3T AND type = "Platform Test Template" AND labels = "{}" AND issue IN linkedIssues({}, "Blocks")'
# }}}
# {{{ list_test_cases() - List active test cases for a specific product
def list_test_cases(args):
user, password = login('jira', args.user)
if not user or not password: sys.exit(1)
jira = Jira().login(user, password)
print(u'List of active test cases for {}:'.format(yellow(product_map[args.PRODUCT])))
if args.verbose:
print(faint('[{}]'.format(QUERY_LIST_TEST_CASES.format(args.PRODUCT))))
print()
has_tests = False
for issue in jira.search(QUERY_LIST_TEST_CASES, args.PRODUCT):
print(u' - {} {}'.format(blue(issue.key), issue.fields.summary))
has_tests = True
if not has_tests:
print(u' - No active test cases found')
print()
# }}}
# {{{ show_status() - Show the status of all test runs for a specific release or release candidate
def show_status(args):
user, password = login('jira', args.user)
if not user or not password: sys.exit(1)
jira = Jira().login(user, password)
version = '{} {}'.format(product_map[args.PRODUCT], args.VERSION).replace('.0', '')
candidate = '{} RC{:02d}'.format(version, args.CANDIDATE) if args.CANDIDATE else version
if args.CANDIDATE:
print(u'Status of test runs for {} version {} release candidate {}:'.format(yellow(product_map[args.PRODUCT]), yellow(args.VERSION), yellow('RC{:02d}'.format(args.CANDIDATE))))
else:
print(u'Status of test runs for {} version {}:'.format(yellow(product_map[args.PRODUCT]), yellow(args.VERSION)))
if args.verbose:
print(faint('[{}]'.format(QUERY_LIST_TEST_INSTANCES.format(args.PRODUCT, version))))
print()
has_tests = False
for issue in jira.search(QUERY_LIST_TEST_INSTANCES, args.PRODUCT, version):
status = issue.fields.status['name'].lower()
if status == 'pass':
status = on_green('Pass')
elif status == 'fail':
status = on_red('Fail')
elif status == 'descope':
status = on_green('Descoped')
else:
status = ''
print(u' - {} {} {}'.format(blue(issue.key), issue.fields.summary, status))
has_test_runs = False
if args.CANDIDATE:
if args.verbose:
print(faint(' [{}]'.format(QUERY_LIST_TEST_RUN_FOR_TICKET.format(args.PRODUCT, candidate, issue.key))))
run_list = jira.search(QUERY_LIST_TEST_RUN_FOR_TICKET, args.PRODUCT, candidate, issue.key)
else:
if args.verbose:
print(faint(' [{}]'.format(QUERY_LIST_ALL_TEST_RUNS_FOR_TICKET.format(args.PRODUCT, issue.key))))
run_list = jira.search(QUERY_LIST_ALL_TEST_RUNS_FOR_TICKET, args.PRODUCT, issue.key)
for run in run_list:
has_test_runs = True
print()
status = run.fields.status['name'].lower()
if status == 'pass':
status = on_green('Pass ')
elif status == 'fail':
status = on_red('Fail ')
elif status == 'descope':
status = on_green('Descoped')
elif status == 'in progress':
status = yellow_on_white('Active ')
else:
status = blue_on_white('Open ')
print(u' {} {} ({})'.format(status, faint(run.fields[jira.custom_fields_by_name['Target Version/s']][0]['name']), blue(run.key)))
if not has_test_runs:
print()
print(u' - No release candidate tests found')
print()
has_tests = True
if not has_tests:
print(u' - No test cases found for the specified release')
print()
# }}}
# {{{ create_version() - Create a new JIRA version
def create_version(args):
user, password = login('jira', args.user)
if not user or not password: sys.exit(1)
jira = Jira().login(user, password)
version = '{} {}'.format(product_map[args.PRODUCT], args.VERSION).replace('.0', '')
version = '{} RC{:02d}'.format(version, args.CANDIDATE) if args.CANDIDATE else version
confirm(u'Create new version {}?'.format(yellow(version)), auto_yes=args.yes or args.dry_run)
print()
if not args.dry_run:
for project in ['CORDA', 'ENT', 'ENM', 'R3T', 'CID']:
print(u' - Creating version {} for project {} ...'.format(yellow(version), blue(project)))
try:
jira.jira.create_version(name=version, project=project, description=version)
print(u' {} - Created version for project {}'.format(green('SUCCESS'), blue(project)))
except Exception as error:
print(u' {} - Failed to version: {}'.format(red('FAIL'), error))
print()
# }}}
# {{{ create_release() - Create test cases for a specific version of a product
def create_release(args):
user, password = login('jira', args.user)
if not user or not password: sys.exit(1)
jira = Jira().login(user, password)
version = '{} {}'.format(product_map[args.PRODUCT], args.VERSION).replace('.0', '')
confirm(u'Create test cases for {} version {}?'.format(yellow(product_map[args.PRODUCT]), yellow(args.VERSION)), auto_yes=args.yes or args.dry_run)
if args.verbose:
print(faint('[{}]'.format(QUERY_LIST_TEST_CASES.format(args.PRODUCT))))
print()
has_tests = False
for issue in jira.search(QUERY_LIST_TEST_CASES, args.PRODUCT):
print(u' - {} {}'.format(blue(issue.key), issue.fields.summary))
print()
has_tests = True
print(u' - Creating test case for version {} ...'.format(yellow(args.VERSION)))
if args.verbose:
print(faint(u' [{}]'.format(QUERY_LIST_TEST_INSTANCE_FOR_TICKET.format(args.PRODUCT, version, issue.key))))
has_test_case_for_version = len(list(jira.search(QUERY_LIST_TEST_INSTANCE_FOR_TICKET.format(args.PRODUCT, version, issue.key))))
if has_test_case_for_version:
print(u' {} - Test case for version already exists'.format(yellow('SKIPPED')))
else:
try:
test_case = issue.clone(issuetype='Platform Test', version=version, dry_run=args.dry_run)
print(u' {} - Created ticket {}'.format(green('SUCCESS'), blue(test_case.key)))
except Exception as error:
print(u' {} - Failed to create ticket: {}'.format(red('FAIL'), error))
print()
print(u' - Linking test case to template ...')
try:
jira.link(issue.key, test_case.key, dry_run=args.dry_run)
print(u' {} - Linked {} to {}'.format(green('SUCCESS'), blue(issue.key), blue(test_case.key)))
except Exception as error:
print(u' {} - Failed to link tickets: {}'.format(red('FAIL'), error))
print()
print(u'Copying links from test templates for {} version {}?'.format(yellow(product_map[args.PRODUCT]), yellow(args.VERSION)))
print()
for issue in jira.search(QUERY_LIST_TEST_CASES, args.PRODUCT):
print(u' - {} {}'.format(blue(issue.key), issue.fields.summary))
print()
print(u' - Copying links for test case {} ...'.format(blue(issue.key)))
has_links = False
if args.verbose:
print(faint(u' [{}]'.format(QUERY_LIST_BLOCKING_TEST_CASES.format(args.PRODUCT, issue.key))))
for blocking_issue in jira.search(QUERY_LIST_BLOCKING_TEST_CASES, args.PRODUCT, issue.key):
from_ticket = list(jira.search(QUERY_LIST_TEST_INSTANCE_FOR_TICKET.format(args.PRODUCT, version, issue.key)))
to_ticket = list(jira.search(QUERY_LIST_TEST_INSTANCE_FOR_TICKET.format(args.PRODUCT, version, blocking_issue.key)))
if len(from_ticket) == 0 or len(to_ticket) == 0:
continue
has_links = True
from_key = from_ticket[0].key
to_key = to_ticket[0].key
try:
jira.link(from_key, to_key, Jira.BLOCKS, dry_run=args.dry_run)
print(u' {} - Linked {} to {}'.format(green('SUCCESS'), blue(from_key), blue(to_key)))
except Exception as error:
print(u' {} - Failed to link tickets {} and {}: {}'.format(red('FAIL'), blue(from_key), blue(to_key), error))
if not has_links:
print(u' {} - No relevant links found for ticket {}'.format(yellow('SKIPPED'), blue(issue.key)))
print()
if not has_tests:
print(u' - No active test cases found')
print()
# }}}
# {{{ create_release_candidate() - Create test run tickets for a specific release candidate of a product
def create_release_candidate(args):
user, password = login('jira', args.user)
if not user or not password: sys.exit(1)
jira = Jira().login(user, password)
version = '{} {}'.format(product_map[args.PRODUCT], args.VERSION).replace('.0', '')
CANDIDATE = args.CANDIDATE[0]
candidate = '{} RC{:02d}'.format(version, CANDIDATE)
confirm(u'Create test run tickets for {} version {} release candidate {}?'.format(yellow(product_map[args.PRODUCT]), yellow(args.VERSION), yellow('RC{:02d}'.format(CANDIDATE))), auto_yes=args.yes or args.dry_run)
if args.verbose:
print(faint('[{}]'.format(QUERY_LIST_TEST_INSTANCES.format(args.PRODUCT, version))))
print()
has_tests = False
for issue in jira.search(QUERY_LIST_TEST_INSTANCES, args.PRODUCT, version):
print(u' - {} {}'.format(blue(issue.key), issue.fields.summary))
epic_field = jira.custom_fields_by_name['Epic Link']
epic = issue.fields[epic_field] if epic_field in issue.fields.to_dict() else ''
labels = issue.fields.labels + [epic]
print()
has_tests = True
print(u' - Creating test run ticket for release candidate {} ...'.format(yellow('RC{:02d}'.format(CANDIDATE))))
if args.verbose:
print(faint(u' [{}]'.format(QUERY_LIST_TEST_RUN_FOR_TICKET.format(args.PRODUCT, candidate, issue.key))))
has_test_instance_for_version = len(list(jira.search(QUERY_LIST_TEST_RUN_FOR_TICKET.format(args.PRODUCT, candidate, issue.key))))
if has_test_instance_for_version:
print(u' {} - Ticket for release candidate already exists'.format(yellow('SKIPPED')))
else:
try:
test_case = issue.clone(issuetype='Platform Test Run', version=candidate, parent=issue.key, labels=labels, dry_run=args.dry_run)
print(u' {} - Created ticket {}'.format(green('SUCCESS'), blue(test_case.key)))
except Exception as error:
print(u' {} - Failed to create ticket: {}'.format(red('FAIL'), error))
print()
if not has_tests:
print(u' - No active test cases found')
print()
# }}}
# {{{ main() - Entry point
def main():
with Program(description='tool for managing test cases and test runs in JIRA') as program:
PRODUCTS = ['OS', 'ENT', 'NS', 'TEST']
program.add('--verbose', '-v', help='turn on verbose logging', action='store_true')
program.add('--yes', '-y', help='automatically answer "yes" to all prompts', action='store_true')
program.add('--user', '-u', help='the user name or email address used to log in to JIRA', type=str, metavar='USER')
program.add('--no-keyring', help='do not retrieve passwords persisted in the keyring', action='store_true')
def mixin_dry_run(command):
command.add('--dry-run', '-d', help='run action without applying any changes to JIRA', action='store_true')
def mixin_product(command):
command.add('PRODUCT', help='the product under test (OS, ENT, NS)', choices=PRODUCTS, metavar='PRODUCT')
def mixin_version_and_product(command):
mixin_product(command)
command.add('VERSION', help='the target version of the release, e.g., 4.0', type=float)
def mixin_candidate(command, optional=False):
if optional:
nargs = '?'
else:
nargs = 1
command.add('CANDIDATE', help='the number of the release candidate, e.g., 1 for RC01', type=int, nargs=nargs)
with program.command('list-tests', 'list test cases applicable to the provided specification', list_test_cases) as command:
mixin_product(command)
with program.command('status', 'show the status of all test runs for a specific release or release candidate', show_status) as command:
mixin_version_and_product(command)
mixin_candidate(command, True)
with program.command('create-version', 'create a new version in JIRA', create_version) as command:
mixin_dry_run(command)
mixin_version_and_product(command)
mixin_candidate(command, True)
with program.command('create-release-tests', 'create test cases for a new release in JIRA', create_release) as command:
mixin_dry_run(command)
mixin_version_and_product(command)
with program.command('create-release-candidate-tests', 'create test runs for a new release candidate in JIRA', create_release_candidate) as command:
mixin_dry_run(command)
mixin_version_and_product(command)
mixin_candidate(command)
# }}}
if __name__ == '__main__': main()