Related
I am writing a script to get the Build related changes in azure devops and it expects argument which is added in the azure deployment file to run the script. That program works as expected, but I want to change it so that if the Build is triggered by PR it should show the PR source branch and PR target branch values and if the Build is triggered by the branch it should show the Source branch only.
Running script with arguments as below in deployment
script: python3 $(Python_file.secureFilePath) $(System.AccessToken) $(Build.Repository.Name) $(Build.DefinitionName) $(Build.BuildNumber) "$(Build.SourceBranchName)""$(System.PullRequest.SourceBranch)" "$(System.PullRequest.TargetBranch)"
My Previous code is some thing like this below
import
sys
pat = sys.argv[1]
repo_name = sys.argv[2]
pipeline_name = sys.argv[3]
build_number = sys.argv[4]
Source_Branch = sys.argv[5]
PR_Source_Branch = sys.argv[6]
PR_Target_Branch = sys.argv[7]
Am doing changes some thing like this below which is not working please let me know how it should be
import sys
Source_Branch =
{
def get_arg(Source_Branch):
try:
sys.argv[5]
except sysargvError:
return ''
else:
return sys.argv[5]
}
PR_Source_Branch =
{
def get_arg(PR_Source_Branch):
try:
sys.argv[6]
except sysargvError:
return ''
else:
return sys.argv[6]
}
PR_Target_Branch =
{
def get_arg(PR_Target_Branch):
try:
sys.argv[7]
except sysargvError:
return ''
else:
return sys.argv[7]
}
expected :
if PR is raised for source Branch to target Branch after merge it will trigger the Build then it should pick up
PR_Source_Branch = sys.argv[6]
PR_Target_Branch = sys.argv[7]
if the Build was run from Branch (Manually) then it should pick up as below because it will not have the source and target Branch's as it was not triggered by PR it will not have that values
Source_Branch = sys.argv[5]
Python "try" doesn't have an "else" clause. Also this is not the error you need to catch. Perhaps something like this:
Source_Branch = {
def get_arg(Source_Branch):
try:
return sys.argv[5]
except IndexError:
return ''
}
So I have an application that generally does a good job at collecting information and moving stuff from one AWS s3 bucket to another, and then processing it, but it doesn't really do a good job when people name their file with a pretext string.
Currently, I look for glob:
dump_files = glob.glob('docker-support*.zip')
What this does is I have logic built to only look for things that account for file names that utilize docker-support as the main identifier.
However, I need it to account for times when people do something like
super_Secret123-Production-whatever-docker-support*.zip
Basically, I would like for the function to rename it using that variable dump_files
Should I just set the variable to something like this:
dump_files = glob.glob('*docker-support*.zip')
or
dump_files = glob.glob('/^(.*?)\docker-support*.zip')
The main thing is I am going to want to pick it up, rename it and then strip the part of the file name that is before the actual file name needed for processing: docker-support*.zip as the application needs to look for files in S3 just named in that format.
Code that handles this:
#!/usr/bin/env python3
# main execution loop for dump analysis tool
# Author: Bryce Ryan, Mirantis Inc.
#
# checks for new files in dump_originals, when found, runs run-parts against that file
# v1.1
# pause.main.loop check
# improved error handling
# escape file name to run-parts to avoid metacharacters
#
#
import os
import tempfile
import time
import zipfile
import logging
import shutil
import glob
from datetime import date
import sys
from os import path
logging.basicConfig(filename='/dump/logs/analyzer_logs.txt', level=logging.DEBUG, format='%(asctime)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%S%z' )
ROOT_DIR = os.path.abspath('..')
logging.debug("ROOT_DIR: {}".format(ROOT_DIR))
DUMP_DIR = os.path.join(ROOT_DIR, 'dump_originals')
logging.debug("DUMP_DIR: {}".format(DUMP_DIR))
WORK_DIR = os.path.join(ROOT_DIR, 'work_dir')
logging.debug("WORK_DIR: {}".format(WORK_DIR))
# can we actually create a file? just because we have perms, or think we do, doesn't mean there are
# enough inodes or capacity to do basic stuff.
with open(os.path.join(DUMP_DIR, "testfile"), 'w'):
pass
logging.info("Beginning event loop for lodestone. Looking for new files in {}".format(DUMP_DIR))
print("Beginning event loop for lodestone.")
sys.stdout.flush()
os.chdir(DUMP_DIR)
logging.basicConfig(filename="analyzer.logs", level=logging.DEBUG)
while True:
# here at the top of the loop, check to see if we should wait for a bit
# typically, because of testing or maintenance
# if the magic, undocumented file exists, wait for 5 sec and check again
# do this forever
while path.exists("/dump/pause.main.loop"):
print("Pausing main loop for 60s, waiting on /dump/pause.main.loop")
time.sleep(60)
dump_files = glob.glob('docker-support*.zip')
try:
if dump_files[0] != '':
logging.debug("files found")
print("================== BEGIN PROCESSING NEW FILE ========= ")
print("File found:", dump_files[0])
print(" ")
logging.info("Processing new file: " + dump_files[0] )
sys.stdout.flush()
support_dump_file = dump_files[0]
# check that it's an actual zip; if not, ignore it
if not zipfile.is_zipfile(support_dump_file):
print("File: " + str(support_dump_file))
print("Inbound file is not recognized as a zip file.\n\n")
logging.info("Inbound file not recognized as a zip file.")
# now move it out of the way so we don't see it again;
# ok if exists on destination and we ignore the error
shutil.move( support_dump_file, "../dump_complete/" )
# no further processing, so back to the top
sys.stdout.flush()
continue
temp_dir = tempfile.mkdtemp(prefix='dump.', dir=WORK_DIR)
os.chmod(temp_dir, 0o777)
logging.info("temp_dir is: " + temp_dir)
# cmd = ROOT_DIR + "/utilities/run-parts --exit-on-error --arg=analyze --arg=" + DUMP_DIR + " --arg=" + support_dump_file + " --arg=" + temp_dir + " " + ROOT_DIR + "/analysis"
cmd = ROOT_DIR + "/utilities/run-parts --arg=analyze --arg=" + DUMP_DIR + " --arg=\'" + support_dump_file + "\' --arg=" + temp_dir + " " + ROOT_DIR + "/analysis"
print(cmd)
logging.info("Will execute: " + cmd )
sys.stdout.flush()
try:
retcode = os.system(cmd)
tempdir =temp_dir
if retcode == 1:
print("Removing temporary work_dir")
logging.debug("Removing temporary work_dir", tempdir)
shutil.rmtree(tempdir, ignore_errors=True)
sys.stdout.flush()
finally:
print("Finally block for cmd. . .")
print("Removing temporary work_dir")
logging.debug("Removing work_dir " + tempdir)
print(tempdir)
sys.stdout.flush()
# shutil.rmtree(tempdir, ignore_errors=True)
os.system('/bin/rm -rf' + tempdir)
sys.stdout.flush()
except:
pass
# pause for a moment; save some processor cycles
sys.stdout.flush()
time.sleep(1)
Right now I do not have the function that will rename this in there.
I am trying to import the snmpSessionBaseClass python module in a script I am running, but I do not have the module installed and I can't seem to find where to download it. Does anyone know the pip or yum command to download and install this module? Thanks!
import netsnmp
sys.path.insert(1, os.path.join(sys.path[0], os.pardir))
from snmpSessionBaseClass import add_common_options, get_common_options, verify_host, get_data
from pynag.Plugins import PluginHelper,ok,critical
The following code needs to be added to a file called snmpSessionBaseClass.py and that file needs to be placed in a directory that is in pythons path.
#!/usr/bin/env python
# Copyright (C) 2016 rsmuc <rsmuc#mailbox.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with health_monitoring_plugins. If not, see <http://www.gnu.org/licenses/>.
import pynag
import netsnmp
import os
import sys
dev_null = os.open(os.devnull, os.O_WRONLY)
tmp_stdout = os.dup(sys.stdout.fileno())
def dev_null_wrapper(func, *a, **kwargs):
"""
Temporarily swap stdout with /dev/null, and execute given function while stdout goes to /dev/null.
This is useful because netsnmp writes to stdout and disturbes Icinga result in some cases.
"""
os.dup2(dev_null, sys.stdout.fileno())
return_object = func(*a, **kwargs)
sys.stdout.flush()
os.dup2(tmp_stdout, sys.stdout.fileno())
return return_object
def add_common_options(helper):
# Define the common command line parameters
helper.parser.add_option('-H', help="Hostname or ip address", dest="hostname")
helper.parser.add_option('-C', '--community', dest='community', help='SNMP community of the SNMP service on target host.', default='public')
helper.parser.add_option('-V', '--snmpversion', dest='version', help='SNMP version. (1 or 2)', default=2, type='int')
def get_common_options(helper):
# get the common options
host = helper.options.hostname
version = helper.options.version
community = helper.options.community
return host, version, community
def verify_host(host, helper):
if host == "" or host is None:
helper.exit(summary="Hostname must be specified"
, exit_code=pynag.Plugins.unknown
, perfdata='')
netsnmp_session = dev_null_wrapper(netsnmp.Session,
DestHost=helper.options.hostname,
Community=helper.options.community,
Version=helper.options.version)
try:
# Works around lacking error handling in netsnmp package.
if netsnmp_session.sess_ptr == 0:
helper.exit(summary="SNMP connection failed"
, exit_code=pynag.Plugins.unknown
, perfdata='')
except ValueError as error:
helper.exit(summary=str(error)
, exit_code=pynag.Plugins.unknown
, perfdata='')
# make a snmp get, if it fails (or returns nothing) exit the plugin
def get_data(session, oid, helper, empty_allowed=False):
var = netsnmp.Varbind(oid)
varl = netsnmp.VarList(var)
data = session.get(varl)
value = data[0]
if value is None:
helper.exit(summary="snmpget failed - no data for host "
+ session.DestHost + " OID: " +oid
, exit_code=pynag.Plugins.unknown
, perfdata='')
if not empty_allowed and not value:
helper.exit(summary="snmpget failed - no data for host "
+ session.DestHost + " OID: " +oid
, exit_code=pynag.Plugins.unknown
, perfdata='')
return value
# make a snmp get, but do not exit the plugin, if it returns nothing
# be careful! This funciton does not exit the plugin, if snmp get fails!
def attempt_get_data(session, oid):
var = netsnmp.Varbind(oid)
varl = netsnmp.VarList(var)
data = session.get(varl)
value = data[0]
return value
# make a snmp walk, if it fails (or returns nothing) exit the plugin
def walk_data(session, oid, helper):
tag = []
var = netsnmp.Varbind(oid)
varl = netsnmp.VarList(var)
data = list(session.walk(varl))
if len(data) == 0:
helper.exit(summary="snmpwalk failed - no data for host " + session.DestHost
+ " OID: " +oid
, exit_code=pynag.Plugins.unknown
, perfdata='')
for x in range(0, len(data)):
tag.append(varl[x].tag)
return data, tag
# make a snmp walk, but do not exit the plugin, if it returns nothing
# be careful! This function does not exit the plugin, if snmp walk fails!
def attempt_walk_data(session, oid):
tag = []
var = netsnmp.Varbind(oid)
varl = netsnmp.VarList(var)
data = list(session.walk(varl))
for x in range(0, len(data)):
tag.append(varl[x].tag)
return data, tag
def state_summary(value, name, state_list, helper, ok_value = 'ok', info = None):
"""
Always add the status to the long output, and if the status is not ok (or ok_value),
we show it in the summary and set the status to critical
"""
# translate the value (integer) we receive to a human readable value (e.g. ok, critical etc.) with the given state_list
state_value = state_list[int(value)]
summary_output = ''
long_output = ''
if not info:
info = ''
if state_value != ok_value:
summary_output += ('%s status: %s %s ' % (name, state_value, info))
helper.status(pynag.Plugins.critical)
long_output += ('%s status: %s %s\n' % (name, state_value, info))
return (summary_output, long_output)
def add_output(summary_output, long_output, helper):
"""
if the summary output is empty, we don't add it as summary, otherwise we would have empty spaces (e.g.: '. . . . .') in our summary report
"""
if summary_output != '':
helper.add_summary(summary_output)
helper.add_long_output(long_output)
I am trying to generate a custom report with pytest and trying to access the assert message generated by pytest, in the case of a failure, in the finalizer of a global fixture in the conftest.py file. I am able to access the status of the test but I am not able to get the error message.
I would like to access the status message in the following way
#pytest.fixture(scope='function',autouse = True)
def logFunctionLevel(request):
start = int(time.time() * 1000)
def fin():
stop = int(time.time())
fo = open("/Users/mahesh.nayak/Desktop/logs/test1.log", "a")
fo.write(request.cls.__name__ + "." + request.function.__name__ + " " + str(start) + " " + str(stop) + "\n")
Any help to access the exception message is appreciated
Thanks
Edit : The answer by Bruno did help. Adding the below lines printed the asserts.
l = str(report.longrepr)
fo.write(l)
I'm not sure you can access the exception message from a fixture, but you can implement a custom pytest_runtest_logreport hook (untested):
def pytest_runtest_logreport(report):
fo = open("/Users/mahesh.nayak/Desktop/logs/test1.log", "a")
fo.write('%s (duration: %s)\n' % (report.nodeid, report.duration))
fo.close()
Hope that helps.
To access the assert message from a fixture you can follow the documentation here :
https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
Which for your question make something look like that: (untested code)
# content of conftest.py
import pytest
#pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result()
# set a report attribute for each phase of a call, which can
# be "setup", "call", "teardown"
setattr(item, "rep_" + rep.when, rep)
#pytest.fixture
def something(request):
yield
# request.node is an "item" because we use the default
# "function" scope
error_message= ""
if request.node.rep_setup.failed:
print("setting up a test failed!", request.node.nodeid)
error_message = request.node.rep_setup.longreprtext
elif request.node.rep_setup.passed:
if request.node.rep_call.failed:
print("executing test failed", request.node.nodeid)
error_message = request.node.rep_call.longreprtext
fo = open("/Users/mahesh.nayak/Desktop/logs/test1.log", "a")
if error_message:
fo.write('%s (duration: %s) - ERROR - %s \n' % (report.nodeid, report.duration,
error_message))
else:
fo.write('%s (duration: %s) - PASSED \n' % (report.nodeid, report.duration))
fo.close()
The main difference with Bruno Oliveira answer, is that pytest_runtest_logreport is called for each different step of the test ( setup/call/teardown? ), when a fixture can be called only one time at the end of the test.
This question was asked by a different user earlier:
Copying Test Cases and Test Folder using Rally Python or Ruby API [closed]
but closed by moderators as being an overly broad question. However, given the inability to copy Test Folders and their member Test Cases within the Rally UI, this is a common need for Rally Users.
Thus - I'll re-pose the question, hopefully with enough detail to stand as a valid question. I'll also re-post the answers that I developed for the original question.
Question: As a Rally user and developer in the Rally Python and Ruby REST APIs: how can I leverage the Rally API toolkits to accomplish this task?
Python:
Here is a script that performs this task - it will copy all Test Cases from a Source Test Folder identified by FormattedID, to a Target Test Folder, also identified by FormattedID. It will copy all Test Steps and Attachments as well. The Target Test Folder must exist, i.e. the script will not create a Test Folder for you if the Target is not found.
The script does not associate the new Test Case to original Test Case's Work Product (i.e. Defect, User Story), or copy Discussion items, Test Case Results, or Last Build, Verdict, etc. as it is assumed the new Test Case is desired to be in a "blank" state.
For those needing to install and configure the Rally Python REST Library:
Rally Developer Portal: Pyral Download and Installation
Pyral Documentation
#!/usr/bin/env python
#################################################################################################
#
# copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
# and attachments. Target Test Folder must exist (i.e. the script will not
# create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64
from pprint import pprint
# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError
errout = sys.stderr.write
my_server = "rally1.rallydev.com"
my_user = "user#company.com"
my_password = "topsecret"
my_workspace = "My Workspace"
my_project = "My Project"
source_test_folder_formatted_id = "TF1"
target_test_folder_formatted_id = "TF4"
rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
# rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project, debug=True)
rally.enableLogging('copy_test_folder.log')
# Query for source and target test folders
source_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % source_test_folder_formatted_id)
target_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % target_test_folder_formatted_id)
# Check to make sure folders exist
if source_test_folder_response.resultCount == 0:
errout('No Source Test Folder Found matching Formatted ID: %s\n' % (source_test_folder_formatted_id))
sys.exit(4)
if target_test_folder_response.resultCount == 0:
errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (target_test_folder_formatted_id))
sys.exit(4)
# Get references to source Test Folder and Test Cases, etc.
source_test_folder = source_test_folder_response.next()
source_test_cases = source_test_folder.TestCases
# Get reference to target Test Folder
target_test_folder = target_test_folder_response.next()
for source_test_case in source_test_cases:
# Create update fields for target Test Case
# Does NOT associate new Test Case to original Test Case's work product (i.e. Defect, User Story)
# Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
# Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
# be "blank" and not have any results associated to it
if source_test_case.Owner != None:
target_owner = source_test_case.Owner.ref
else:
target_owner = None
target_test_case_fields = {
"Package": source_test_case.Package,
"Description": source_test_case.Description,
"Method": source_test_case.Method,
"Name": source_test_case.Name,
"Objective": source_test_case.Objective,
"Owner": target_owner,
"PostConditions": source_test_case.PostConditions,
"PreConditions": source_test_case.PreConditions,
"Priority": source_test_case.Priority,
"Project": source_test_case.Project.ref,
"Risk": source_test_case.Risk,
"ValidationInput": source_test_case.ValidationInput,
"ValidationExpectedResult": source_test_case.ValidationExpectedResult,
"TestFolder": target_test_folder.ref,
}
# Create the target test case
try:
target_test_case = rally.create("TestCase", target_test_case_fields)
message = "Copied Source Test Case: " + source_test_case.FormattedID + \
" To: " + target_test_folder.FormattedID + ": " + target_test_folder.Name + \
": " + target_test_case.FormattedID
print message
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Test Steps
# Add Test Case Steps
source_test_case_steps = source_test_case.Steps
for source_step in source_test_case_steps:
target_step_fields = {
"TestCase" : target_test_case.ref,
"StepIndex" : source_step.StepIndex,
"Input" : source_step.Input,
"ExpectedResult" : source_step.ExpectedResult
}
target_test_case_step = rally.put('TestCaseStep', target_step_fields)
print "===> Copied TestCaseStep: %s OID: %s" % (target_test_case_step.StepIndex, target_test_case_step.oid)
# Copy Attachments
source_attachments = rally.getAttachments(source_test_case)
for source_attachment in source_attachments:
# First copy the content
source_attachment_content = source_attachment.Content
target_attachment_content_fields = {
"Content": base64.encodestring(source_attachment_content)
}
try:
target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Next copy the attachment object
target_attachment_fields = {
"Name": source_attachment.Name,
"Description": source_attachment.Description,
"Content": target_attachment_content.ref,
"ContentType": source_attachment.ContentType,
"Size": source_attachment.Size,
"Artifact": target_test_case.ref,
"User": source_attachment.User.ref
}
try:
target_attachment = rally.put('Attachment', target_attachment_fields)
print "===> Copied Attachment: %s" % target_attachment.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Tags
source_tags = source_test_case.Tags;
target_tags = list()
for source_tag in source_tags:
target_tags.append({"_ref":source_tag.ref})
target_test_case_fields = {
"FormattedID": target_test_case.FormattedID,
"Tags": target_tags
}
try:
update_response = rally.update('TestCase', target_test_case_fields)
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
Ruby:
This Ruby script will copy all Test Cases from a Source Test Folder identified by FormattedID, to a Target Test Folder, also identified by FormattedID. It will copy all Test Steps and Attachments as well. The Target Test Folder must exist, i.e. the script will not create a Test Folder for you if the Target is not found.
The script does not associate the new Test Case to original Test Case's Work Product (i.e. Defect, User Story), or copy Discussion items, Test Case Results, or Last Build, Verdict, etc. as it is assumed the new Test Case is desired to be in a "blank" state.
For those needing to install and configure the Ruby REST Toolkit, links are here:
Developer Portal: Rally REST API for Ruby
Github
# Copyright 2002-2012 Rally Software Development Corp. All Rights Reserved.
require 'rally_api'
$my_base_url = "https://rally1.rallydev.com/slm"
$my_username = "user#company.com"
$my_password = "password"
$my_workspace = "My Workspace"
$my_project = "My Project"
$wsapi_version = "1.37"
# Test Folders
$source_test_folder_formatted_id = "TF4"
$target_test_folder_formatted_id = "TF8"
# Load (and maybe override with) my personal/private variables from a file...
my_vars= File.dirname(__FILE__) + "/my_vars.rb"
if FileTest.exist?( my_vars ) then require my_vars end
#==================== Make a connection to Rally ====================
config = {:base_url => $my_base_url}
config[:username] = $my_username
config[:password] = $my_password
config[:workspace] = $my_workspace
config[:project] = $my_project
config[:version] = $wsapi_version
#rally = RallyAPI::RallyRestJson.new(config)
begin
# Lookup source Test Folder
source_test_folder_query = RallyAPI::RallyQuery.new()
source_test_folder_query.type = :testfolder
source_test_folder_query.fetch = true
source_test_folder_query.query_string = "(FormattedID = \"" + $source_test_folder_formatted_id + "\")"
source_test_folder_result = #rally.find(source_test_folder_query)
# Lookup Target Test Folder
target_test_folder_query = RallyAPI::RallyQuery.new()
target_test_folder_query.type = :testfolder
target_test_folder_query.fetch = true
target_test_folder_query.query_string = "(FormattedID = \"" + $target_test_folder_formatted_id + "\")"
target_test_folder_result = #rally.find(target_test_folder_query)
if source_test_folder_result.total_result_count == 0
puts "Source Test Folder: " + $source_test_folder_formatted_id + "not found. Exiting."
exit
end
if target_test_folder_result.total_result_count == 0
puts "Target Test Folder: " + $target_test_folder_formatted_id + "not found. Target must exist before copying."
exit
end
source_test_folder = source_test_folder_result.first()
target_test_folder = target_test_folder_result.first()
# Populate full object for Target Test Folder
full_target_test_folder = target_test_folder.read
# Get Target Project
target_project = full_target_test_folder["Project"]
# Grab collection of Source Test Cases
source_test_cases = source_test_folder["TestCases"]
# Loop through Source Test Cases and Copy to Target
source_test_cases.each do |source_test_case|
# Get full object for Source Test Case
full_source_test_case = source_test_case.read
# Check if there's an Owner
if !full_source_test_case["Owner"].nil?
source_owner = full_source_test_case["Owner"]
else
source_owner = nil
end
# Populate field data from Source to Target
target_test_case_fields = {}
target_test_case_fields["Package"] = full_source_test_case["Package"]
target_test_case_fields["Description"] = full_source_test_case["Description"]
target_test_case_fields["Method"] = full_source_test_case["Method"]
target_test_case_fields["Name"] = full_source_test_case["Name"]
target_test_case_fields["Objective"] = full_source_test_case["Objective"]
target_test_case_fields["Owner"] = source_owner
target_test_case_fields["PostConditions"] = full_source_test_case["PostConditions"]
target_test_case_fields["PreConditions"] = full_source_test_case["PreConditions"]
target_test_case_fields["Priority"] = full_source_test_case["Priority"]
target_test_case_fields["Project"] = target_project
target_test_case_fields["Risk"] = full_source_test_case["Risk"]
target_test_case_fields["ValidationInput"] = full_source_test_case["ValidationInput"]
target_test_case_fields["ValidationExpectedResult"] = full_source_test_case["ValidationExpectedResult"]
target_test_case_fields["Tags"] = full_source_test_case["Tags"]
target_test_case_fields["TestFolder"] = target_test_folder
# Create the Target Test Case
begin
target_test_case = #rally.create(:testcase, target_test_case_fields)
puts "Test Case: #{full_source_test_case["FormattedID"]} successfully copied to #{full_target_test_folder["FormattedID"]}"
rescue => ex
puts "Test Case: #{full_source_test_case["FormattedID"]} not copied due to error"
puts ex
end
# Now Copy Test Steps
# Add Test Case Steps
source_test_case_steps = full_source_test_case["Steps"]
source_test_case_steps.each do |source_test_case_step|
full_source_step = source_test_case_step.read
target_step_fields = {}
target_step_fields["TestCase"] = target_test_case
target_step_fields["StepIndex"] = full_source_step["StepIndex"]
target_step_fields["Input"] = full_source_step["Input"]
target_step_fields["ExpectedResult"] = full_source_step["ExpectedResult"]
begin
target_test_case_step = #rally.create(:testcasestep, target_step_fields)
puts "===> Copied TestCaseStep: #{target_test_case_step["_ref"]}"
rescue => ex
puts "Test Case Step not copied due to error:"
puts ex
end
end
# Now Copy Attachments
source_attachments = full_source_test_case["Attachments"]
source_attachments.each do |source_attachment|
full_source_attachment = source_attachment.read
source_attachment_content = full_source_attachment["Content"]
full_source_attachment_content = source_attachment_content.read
# Create AttachmentContent Object for Target
target_attachment_content_fields = {}
target_attachment_content_fields["Content"] = full_source_attachment_content["Content"]
begin
target_attachment_content = #rally.create(:attachmentcontent, target_attachment_content_fields)
puts "===> Copied AttachmentContent: #{target_attachment_content["_ref"]}"
rescue => ex
puts "AttachmentContent not copied due to error:"
puts ex
end
# Now Create Attachment Container
target_attachment_fields = {}
target_attachment_fields["Name"] = full_source_attachment["Name"]
target_attachment_fields["Description"] = full_source_attachment["Description"]
target_attachment_fields["Content"] = target_attachment_content
target_attachment_fields["ContentType"] = full_source_attachment["ContentType"]
target_attachment_fields["Size"] = full_source_attachment["Size"]
target_attachment_fields["Artifact"] = target_test_case
target_attachment_fields["User"] = full_source_attachment["User"]
begin
target_attachment = #rally.create(:attachment, target_attachment_fields)
puts "===> Copied Attachment: #{target_attachment["_ref"]}"
rescue => ex
puts "Attachment not copied due to error:"
puts ex
end
end
end
end
An improvement of the same Python script that adds the following features
Test Folders (source and destination) are passed in arguments instead of hard coded
Creation is done recursively (i.e. copy Test Folders and Test Cases in it)
Test Cases are replaced if they already exist (based on the Name)
Usage of API Key to connect rather than password
Try 5 times to connect to Rally before giving up
To use it to copy TF10 to TF99 (which already exists): python copy_test_folder TF10 TF99
#!/usr/bin/env python
#################################################################################################
#
# copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
# and attachments. Target Test Folder must exist (i.e. the script will not
# create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py TF_src TF_dest
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64
from pprint import pprint
# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError
errout = sys.stderr.write
my_server = "rally1.rallydev.com"
my_user = "name#domain.com"
my_password = "<PASSWORD>"
my_workspace = "<WORKSPACE>"
my_project = "<PROJECT>"
my_key = "<API KEY>"
def connect(nb_attempts=5):
'''Connect to Rally'''
global is_connected
global rally
#Connect
if not is_connected and nb_attempts>0:
print "...Attempting to connect to Rally for project %s..." % my_project
try:
# rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
rally = Rally(my_server, apikey=my_key, workspace=my_workspace, project=my_project)
print "Connected to Rally for project %s (%s)" % (my_project, my_workspace)
is_connected=True
rally.enableLogging('copy_test_folder.log')
#Errors during connection (attempting to connect again)
# except Exception, details:
except AttributeError:
if nb_attempts>1:
connect(nb_attempts-1)
else:
errout('Error during connection to Rally (%s)\n' % details)
exit(4)
else: pass
def copyTF(src_TF, dest_TF):
'''Copy Test Cases from one folder into another (including children Test Folders)'''
# List names and FormattedID of existing Test Cases in destination
dest_TCs = dest_TF.TestCases
existing_dest_TC = {tc.Name:tc.FormattedID for tc in dest_TCs}
# Copy Test Cases to destination folder (replace if Test Case with same name exists)
src_TCs = src_TF.TestCases
for src_TC in src_TCs:
# Create update fields for target Test Case
# Does NOT associate new Test Case to original Test Case's WorkProduct (i.e. Defect, User Story)
# Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
# Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
# be "blank" and not have any results associated to it
tcName = src_TC.Name
dest_TC_fields = {
"Package": src_TC.Package,
"Description": src_TC.Description,
"Method": src_TC.Method,
"Name": tcName,
"Objective": src_TC.Objective,
"Owner": getattr(src_TC.Owner, 'ref', None),
"PostConditions": src_TC.PostConditions,
"PreConditions": src_TC.PreConditions,
"Priority": src_TC.Priority,
"Project": src_TC.Project.ref,
"Risk": src_TC.Risk,
"ValidationInput": src_TC.ValidationInput,
"ValidationExpectedResult": src_TC.ValidationExpectedResult,
"TestFolder": dest_TF.ref,
}
# Create/Update the target test case
try:
if existing_dest_TC.has_key(tcName):
operation = "Update"
dest_TC_fields['FormattedID'] = existing_dest_TC[tcName]
dest_TC = rally.update("TestCase", dest_TC_fields)
else:
#Create
operation = "Create"
dest_TC = rally.create("TestCase", dest_TC_fields)
message = operation + "d Source Test Case: " + src_TC.FormattedID + \
" To: " + dest_TF.FormattedID + ": " + dest_TF.Name + \
": " + dest_TC.FormattedID
print message
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Test Steps
#Cleared-up all Test Steps in destination if Test Case is updated
if operation == "Update":
dest_TC_steps = dest_TC.Steps
for dest_step in dest_TC_steps:
rally.delete('TestCaseStep', dest_step.oid)
print "===> Cleared all Test Steps"
# Add Test Case Steps
src_TC_steps = src_TC.Steps
for src_step in src_TC_steps:
target_step_fields = {
"TestCase" : dest_TC.ref,
"StepIndex" : src_step.StepIndex,
"Input" : src_step.Input,
"ExpectedResult" : src_step.ExpectedResult
}
dest_TC_step = rally.put('TestCaseStep', target_step_fields)
print "===> Copied TestCaseStep: %s OID: %s" % (dest_TC_step.StepIndex, dest_TC_step.oid)
# Copy Attachments
source_attachments = rally.getAttachments(src_TC)
for source_attachment in source_attachments:
# First copy the content
source_attachment_content = source_attachment.Content
target_attachment_content_fields = {
"Content": base64.encodestring(source_attachment_content)
}
try:
target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Next copy the attachment object
target_attachment_fields = {
"Name": source_attachment.Name,
"Description": source_attachment.Description,
"Content": target_attachment_content.ref,
"ContentType": source_attachment.ContentType,
"Size": source_attachment.Size,
"Artifact": dest_TC.ref,
"User": source_attachment.User.ref
}
try:
target_attachment = rally.put('Attachment', target_attachment_fields)
print "===> Copied Attachment: %s" % target_attachment.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Tags
source_tags = src_TC.Tags;
target_tags = list()
for source_tag in source_tags:
target_tags.append({"_ref":source_tag.ref})
dest_TC_fields = {
"FormattedID": dest_TC.FormattedID,
"Tags": target_tags
}
try:
update_response = rally.update('TestCase', dest_TC_fields)
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Recursive call for each child Test Folder (after creating it)
src_children_TFs = src_TF.Children
dest_children_TFs = dest_TF.Children
existing_dest_TF = {tf.Name:tf for tf in dest_children_TFs}
for src_child_TF in src_children_TFs:
tfName = src_child_TF.Name
# Create Test Folder if needed
if existing_dest_TF.has_key(tfName):
dest_child_TF = existing_dest_TF[tfName]
else:
target_TF_fields = {
'Name': tfName,
'Parent': dest_TF.ref
}
dest_child_TF = rally.put('TestFolder', target_TF_fields)
print "Created Test folder %s (%s)" % (tfName, dest_child_TF.FormattedID)
# Copy Test Cases of this folder
copyTF(src_child_TF, dest_child_TF)
if '__main__' in __name__:
# Get source and destination test folders
src_TF_formatted_id = sys.argv[1]
dest_TF_formatted_id = sys.argv[2]
# Connect to Rally
global is_connected
is_connected = False
connect(5)
# Query for source and target test folders
global rally
src_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % src_TF_formatted_id)
dest_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % dest_TF_formatted_id)
# Check to make sure folders exist
if src_TF_response.resultCount == 0:
errout('No Source Test Folder Found matching Formatted ID: %s\n' % (src_TF_formatted_id))
sys.exit(4)
if dest_TF_response.resultCount == 0:
errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (dest_TF_formatted_id))
sys.exit(4)
# Get Objects of source and target Test Folders
src_TF = src_TF_response.next()
dest_TF = dest_TF_response.next()
# Copy file
copyTF(src_TF, dest_TF)