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

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

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

@ -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 :-)

@ -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)
# }}}

@ -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)
# }}}

@ -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)
# }}}

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

@ -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()