Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ how existing code can be adapted
TestLink-API-Python-client developers
-------------------------------------
* `James Stock`_, `Olivier Renault`_, `lczub`_, `manojklm`_ (PY3)
* `g4l4drim`_, `pade`_, `anton-matosov`_, `citizen-stig`_, `charz`_, `Maberi`_
* `g4l4drim`_, `pade`_, `anton-matosov`_, `citizen-stig`_, `charz`_, `Maberi`_, `Brian-Williams`_
* anyone forgotten?

.. _Apache License 2.0: http://www.apache.org/licenses/LICENSE-2.0
Expand Down
2 changes: 1 addition & 1 deletion example/Last7DaysTestCases.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def iterTCasesfromTProject(api, TProjName, date1, date2):
""" returns as iterator all test cases of project TPROJTNAME, which are
created between DATE1 and DATE2
DATE1 and DATE2 must be of type time.struct_time """
TProjId = api.getTestProjectByName(TProjName)[0]['id']
TProjId = api.getTestProjectByName(TProjName)['id']
for TSinfo in api.getFirstLevelTestSuitesForTestProject(TProjId):
TSuiteId = TSinfo['id']
for TCid in api.getTestCasesForTestSuite(TSuiteId, deep=1,details='only_id'):
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,3 @@
keywords = ['testing', 'testlink', 'xml-rpc', 'testautomation']

)

4 changes: 3 additions & 1 deletion src/testlink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@
from .testlinkerrors import TestLinkError
from .testlinkapigeneric import TestlinkAPIGeneric
from .testlinkapi import TestlinkAPIClient
from .testlinkhelper import TestLinkHelper
from .testlinkhelper import TestLinkHelper
from .testreporter import TestReporter
from .testgenreporter import TestGenReporter
28 changes: 28 additions & 0 deletions src/testlink/testgenreporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from .testreporter import AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter


class TestGenReporter(AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter):
"""
This is the default generate everything it can version of test reporting.

This class will always try to report a result. It will generate everything possible and will change with additional
Add*Reporter's added to the repo. As such you should only use this if you want to always generate everything this
repo is capable of. If you want what it does at any specific time you should create this class in your project and
use directly.

If you don't want to generate one of these values you can 'roll your own' version of this class with only the
needed features that you want to generate. As stated above if you *only* want to generate what this class currently
does. Copying it into your project is the best practice as this class is mutable inside the project!

For example if you wanted to add platforms and/or tests to testplans, but didn't want to ever make a new testplan
you could use a class like:
`type('MyOrgTestGenReporter', (AddTestCaseReporter, AddPlatformReporter, TestReporter), {})`

Example usage with fake testlink server test and a manual project.
```
tls = testlink.TestLinkHelper('https://testlink.corp.com/testlink/lib/api/xmlrpc/v1/xmlrpc.php',
'devkeyabc123').connect(testlink.TestlinkAPIClient)
tgr = TestGenReporter(tls, ['TEST-123'], testprojectname='MANUALLY_MADE_PROJECT', testplanname='generated',
platformname='gend', buildname='8.fake', status='p')
```
"""
20 changes: 18 additions & 2 deletions src/testlink/testlinkapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,23 @@ def copyTCnewTestCase(self, origTestCaseId, origVersion=None, **changedAttribute
return self._copyTC(origTestCaseId, changedAttributes, origVersion,
duplicateaction = 'generate_new')


def getTestCaseByVersion(self, testCaseID, version=None):
"""
Gets testcase information based on the version.

:param testCaseID: test case to search for
:param version: version to search for defaults to None. None searches for latest
:return: test case info dictionary
"""
testcases = self.getTestCase(testCaseID, version=version)
if version is None:
return testcases[0]
for testcase in testcases:
if str(testcase['version']) == str(version):
return testcase
else:
raise TLArgError("Testcase {} doesn't have version {}.".format(testCaseID, version))

def _copyTC(self, origTestCaseId, changedArgs, origVersion=None, **options):
""" creates a copy of test case with id ORIGTESTCASEID

Expand All @@ -237,7 +253,7 @@ def _copyTC(self, origTestCaseId, changedArgs, origVersion=None, **options):
"""

# get orig test case content
origArgItems = self.getTestCase(origTestCaseId, version=origVersion)[0]
origArgItems = self.getTestCaseByVersion(origTestCaseId, version=origVersion)
# get orig test case project id
origArgItems['testprojectid'] = self.getProjectIDByNode(origTestCaseId)

Expand Down
5 changes: 4 additions & 1 deletion src/testlink/testlinkhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ def __init__(self, server_url=None, devkey=None, proxy=None):
self._server_url = server_url
self._devkey = devkey
self._proxy = proxy
self._setParams()

def _setParams(self):
self._setParamsFromEnv()

def _setParamsFromEnv(self):
""" fill empty slots from environment variables
_server_url <- TESTLINK_API_PYTHON_SERVER_URL
Expand Down
220 changes: 220 additions & 0 deletions src/testlink/testreporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
from testlink.testlinkerrors import TLResponseError


class TestReporter(dict):
def __init__(self, tls, testcases, *args, **kwargs):
"""This can be given one or more testcases, but they all must have the same project, plan, and platform."""
super(TestReporter, self).__init__(*args, **kwargs)
self.tls = tls
# handle single testcase
self.testcases = testcases if isinstance(testcases, list) else [testcases]
self._plan_testcases = None
self.remove_non_report_kwargs()
self._platformname_generated = False

def remove_non_report_kwargs(self):
self.buildname = self.pop('buildname')
self.buildnotes = self.pop('buildnotes', "Created with automation.")

def setup_testlink(self):
"""Call properties that may set report kwarg values."""
self.testprojectname
self.testprojectid
self.testplanid
self.testplanname
self.platformname
self.platformid
self.buildid

def _get_project_name_by_id(self):
if self.testprojectid:
for project in self.tls.getProjects():
if project['id'] == self.testprojectid:
return project['name']

def _projectname_getter(self):
if not self.get('testprojectname') and self.testprojectid:
self['testprojectname'] = self._get_project_name_by_id()
return self.get('testprojectname')

@property
def testprojectname(self):
return self._projectname_getter()

def _get_project_id(self):
tpid = self.get('testprojectid')
if not tpid and self.testprojectname:
self['testprojectid'] = self.tls.getProjectIDByName(self['testprojectname'])
return self['testprojectid']
return tpid

def _get_project_id_or_none(self):
project_id = self._get_project_id()
# If not found the id will return as -1
if project_id == -1:
project_id = None
return project_id

@property
def testprojectid(self):
self['testprojectid'] = self._get_project_id_or_none()
return self.get('testprojectid')

@property
def testplanid(self):
return self.get('testplanid')

@property
def testplanname(self):
return self.get('testplanname')

@property
def platformname(self):
"""Return a platformname added to the testplan if there is one."""
return self.get('platformname')

@property
def platformid(self):
return self.get('platformid')

@property
def buildid(self):
return self.get('buildid')

@property
def plan_tcids(self):
if not self._plan_testcases:
self._plan_testcases = set()
tc_dict = self.tls.getTestCasesForTestPlan(self.testplanid)
try:
for _, platform in tc_dict.items():
for k, v in platform.items():
self._plan_testcases.add(v['full_external_id'])
except AttributeError:
# getTestCasesForTestPlan returns an empty list instead of an empty dict
pass
return self._plan_testcases

def reportgen(self):
"""For use if you need to look at the status returns of individual reporting."""
self.setup_testlink()
for testcase in self.testcases:
yield self.tls.reportTCResult(testcaseexternalid=testcase, **self)

def report(self):
for _ in self.reportgen():
pass


class AddTestCaseReporter(TestReporter):
"""Add testcase to testplan if not added."""
def setup_testlink(self):
super(AddTestCaseReporter, self).setup_testlink()
self.ensure_testcases_in_plan()

def ensure_testcases_in_plan(self):
# Get the platformid if possible or else addition will fail
self.platformid
for testcase in self.testcases:
# Can't check if testcase is in plan_tcids, because that won't work if it's there, but of the wrong platform
try:
self.tls.addTestCaseToTestPlan(
self.testprojectid, self.testplanid, testcase, self.get_latest_tc_version(testcase),
platformid=self.platformid
)
except TLResponseError as e:
# Test Case version is already linked to Test Plan
if e.code == 3045:
pass
else:
raise

def get_latest_tc_version(self, testcaseexternalid):
return int(self.tls.getTestCase(None, testcaseexternalid=testcaseexternalid)[0]['version'])


class AddTestPlanReporter(TestReporter):
@property
def testplanid(self):
if not self.get('testplanid'):
try:
self['testplanid'] = self.tls.getTestPlanByName(self.testprojectname, self.testplanname)[0]['id']
except TLResponseError as e:
# Name does not exist
if e.code == 3033:
self['testplanid'] = self.generate_testplanid()
else:
raise
except TypeError:
self['testplanid'] = self.generate_testplanid()
return self['testplanid']

def generate_testplanid(self):
"""This won't necessarily be able to create a testplanid. It requires a planname and projectname."""
if 'testplanname' not in self:
raise RuntimeError("Need testplanname to generate a testplan for results.")

tp = self.tls.createTestPlan(self['testplanname'], self.testprojectname)
self['testplanid'] = tp[0]['id']
return self['testplanid']


class AddPlatformReporter(TestReporter):
@property
def platformname(self):
"""Return a platformname added to the testplan if there is one."""
pn_kwarg = self.get('platformname')
if pn_kwarg and self._platformname_generated is False:
# If we try to create platform and catch platform already exists error (12000) it sometimes duplicates a
# platformname
try:
self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
except TLResponseError as e:
if int(e.code) == 235:
self.tls.createPlatform(self.testprojectname, pn_kwarg)
self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
else:
raise
self._platformname_generated = True
return pn_kwarg

@property
def platformid(self):
if not self.get('platformid'):
self['platformid'] = self.getPlatformID(self.platformname)
# This action is idempotent
self.tls.addPlatformToTestPlan(self.testplanid, self.platformname)
return self['platformid']

def getPlatformID(self, platformname, _firstrun=True):
"""
This is hardcoded for platformname to always be self.platformname
"""
platforms = self.tls.getTestPlanPlatforms(self.testplanid)
for platform in platforms:
if platform['name'] == platformname:
return platform['id']
# Platformname houses platform creation as platform creation w/o a name isn't possible
if not self.platformname:
raise RuntimeError(
"Couldn't find platformid for {}.{}, "
"please provide a platformname to generate.".format(self.testplanid, platformname)
)
if _firstrun is True:
return self.getPlatformID(self.platformname, _firstrun=False)
else:
raise RuntimeError("PlatformID not found after generated from platformname '{}' "
"in test plan {}.".format(self.platformname, self.testplanid))


class AddBuildReporter(TestReporter):
@property
def buildid(self):
bid = self.get('buildid')
if not bid or bid not in self.tls.getBuildsForTestPlan(self.testplanid):
self['buildid'] = self._generate_buildid()
return self.get('buildid')

def _generate_buildid(self):
r = self.tls.createBuild(self.testplanid, self.buildname, self.buildnotes)
return r[0]['id']
1 change: 0 additions & 1 deletion src/testlink/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@

VERSION = '0.6.4'
TL_RELEASE = '1.9.16'