Skip to content

Commit 9470568

Browse files
author
Brian Williams
committed
Add TestReporter object
This helps with reporting testcase results. The user can roll their own TestGenReporter, which can generate the needed values to store the execution status.
1 parent 447beb7 commit 9470568

File tree

5 files changed

+246
-4
lines changed

5 files changed

+246
-4
lines changed

Diff for: setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,3 @@
8787
keywords = ['testing', 'testlink', 'xml-rpc', 'testautomation']
8888

8989
)
90-

Diff for: src/testlink/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121
from .testlinkerrors import TestLinkError
2222
from .testlinkapigeneric import TestlinkAPIGeneric
2323
from .testlinkapi import TestlinkAPIClient
24-
from .testlinkhelper import TestLinkHelper
24+
from .testlinkhelper import TestLinkHelper
25+
from .testreporter import TestGenReporter

Diff for: src/testlink/testlinkhelper.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ def __init__(self, server_url=None, devkey=None, proxy=None):
7777
self._server_url = server_url
7878
self._devkey = devkey
7979
self._proxy = proxy
80+
self._setParams()
81+
82+
def _setParams(self):
8083
self._setParamsFromEnv()
81-
84+
8285
def _setParamsFromEnv(self):
8386
""" fill empty slots from environment variables
8487
_server_url <- TESTLINK_API_PYTHON_SERVER_URL

Diff for: src/testlink/testreporter.py

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
from testlink.testlinkerrors import TLResponseError
2+
3+
4+
class TestReporter(dict):
5+
def __init__(self, tls, testcases, *args, **kwargs):
6+
"""This can be given one or more testcases, but they all must have the same project, plan, and platform."""
7+
super(TestReporter, self).__init__(*args, **kwargs)
8+
self.tls = tls
9+
# handle single testcase
10+
self.testcases = testcases if isinstance(testcases, list) else [testcases]
11+
self._plan_testcases = None
12+
self.remove_non_report_kwargs()
13+
self._platformname_generated = False
14+
15+
def remove_non_report_kwargs(self):
16+
self.buildname = self.pop('buildname')
17+
self.buildnotes = self.pop('buildnotes', "Created with automation.")
18+
19+
def setup_testlink(self):
20+
"""Call properties that may set report kwarg values."""
21+
self.testprojectname
22+
self.testprojectid
23+
self.testplanid
24+
self.testplanname
25+
self.platformname
26+
self.platformid
27+
self.buildid
28+
29+
def _get_project_name_by_id(self):
30+
if self.testprojectid:
31+
for project in self.tls.getProjects():
32+
if project['id'] == self.testprojectid:
33+
return project['name']
34+
35+
def _projectname_getter(self):
36+
if not self.get('testprojectname') and self.testprojectid:
37+
self['testprojectname'] = self._get_project_name_by_id()
38+
return self.get('testprojectname')
39+
40+
@property
41+
def testprojectname(self):
42+
return self._projectname_getter()
43+
44+
def _get_project_id(self):
45+
tpid = self.get('testprojectid')
46+
if not tpid and self.testprojectname:
47+
self['testprojectid'] = self.tls.getProjectIDByName(self['testprojectname'])
48+
return self['testprojectid']
49+
return tpid
50+
51+
def _get_project_id_or_none(self):
52+
project_id = self._get_project_id()
53+
# If not found the id will return as -1
54+
if project_id == -1:
55+
project_id = None
56+
return project_id
57+
58+
@property
59+
def testprojectid(self):
60+
self['testprojectid'] = self._get_project_id_or_none()
61+
return self.get('testprojectid')
62+
63+
@property
64+
def testplanid(self):
65+
return self.get('testplanid')
66+
67+
@property
68+
def testplanname(self):
69+
return self.get('testplanname')
70+
71+
@property
72+
def platformname(self):
73+
"""Return a platformname added to the testplan if there is one."""
74+
return self.get('platformname')
75+
76+
@property
77+
def platformid(self):
78+
return self.get('platformid')
79+
80+
@property
81+
def buildid(self):
82+
return self.get('buildid')
83+
84+
@property
85+
def plan_tcids(self):
86+
if not self._plan_testcases:
87+
self._plan_testcases = set()
88+
tc_dict = self.tls.getTestCasesForTestPlan(self.testplanid)
89+
try:
90+
for _, platform in tc_dict.items():
91+
for k, v in platform.items():
92+
self._plan_testcases.add(v['full_external_id'])
93+
except AttributeError:
94+
# getTestCasesForTestPlan returns an empty list instead of an empty dict
95+
pass
96+
return self._plan_testcases
97+
98+
def reportgen(self):
99+
"""For use if you need to look at the status returns of individual reporting."""
100+
self.setup_testlink()
101+
for testcase in self.testcases:
102+
yield self.tls.reportTCResult(testcaseexternalid=testcase, **self)
103+
104+
def report(self):
105+
for _ in self.reportgen():
106+
pass
107+
108+
109+
class AddTestReporter(TestReporter):
110+
"""Add testcase to testplan if not added."""
111+
def setup_testlink(self):
112+
super(AddTestReporter, self).setup_testlink()
113+
self.ensure_testcases_in_plan()
114+
115+
def ensure_testcases_in_plan(self):
116+
# Get the platformid if possible or else addition will fail
117+
self.platformid
118+
for testcase in self.testcases:
119+
# Can't check if testcase is in plan_tcids, because that won't work if it's there, but of the wrong platform
120+
try:
121+
self.tls.addTestCaseToTestPlan(
122+
self.testprojectid, self.testplanid, testcase, self.get_latest_tc_version(testcase),
123+
platformid=self.platformid
124+
)
125+
except TLResponseError as e:
126+
# Test Case version is already linked to Test Plan
127+
if e.code == 3045:
128+
pass
129+
else:
130+
raise
131+
132+
def get_latest_tc_version(self, testcaseexternalid):
133+
return int(self.tls.getTestCase(None, testcaseexternalid=testcaseexternalid)[0]['version'])
134+
135+
136+
class AddTestPlanReporter(TestReporter):
137+
@property
138+
def testplanid(self):
139+
if not self.get('testplanid'):
140+
try:
141+
self['testplanid'] = self.tls.getTestPlanByName(self.testprojectname, self.testplanname)[0]['id']
142+
except TLResponseError as e:
143+
# Name does not exist
144+
if e.code == 3033:
145+
self['testplanid'] = self.generate_testplanid()
146+
else:
147+
raise
148+
except TypeError:
149+
self['testplanid'] = self.generate_testplanid()
150+
return self['testplanid']
151+
152+
def generate_testplanid(self):
153+
"""This won't necessarily be able to create a testplanid. It requires a planname and projectname."""
154+
if 'testplanname' not in self:
155+
raise RuntimeError("Need testplanname to generate a testplan for results.")
156+
157+
tp = self.tls.createTestPlan(self['testplanname'], self.testprojectname)
158+
self['testplanid'] = tp[0]['id']
159+
return self['testplanid']
160+
161+
162+
class AddPlatformReporter(TestReporter):
163+
@property
164+
def platformname(self):
165+
"""Return a platformname added to the testplan if there is one."""
166+
pn_kwarg = self.get('platformname')
167+
if pn_kwarg and self._platformname_generated is False:
168+
# If we try to create platform and catch platform already exists error (12000) it sometimes duplicates a
169+
# platformname
170+
try:
171+
self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
172+
except TLResponseError as e:
173+
if int(e.code) == 235:
174+
self.tls.createPlatform(self.testprojectname, pn_kwarg)
175+
self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
176+
else:
177+
raise
178+
self._platformname_generated = True
179+
return pn_kwarg
180+
181+
@property
182+
def platformid(self):
183+
if not self.get('platformid'):
184+
self['platformid'] = self.getPlatformID(self.platformname)
185+
# This action is idempotent
186+
self.tls.addPlatformToTestPlan(self.testplanid, self.platformname)
187+
return self['platformid']
188+
189+
def getPlatformID(self, platformname, _firstrun=True):
190+
"""
191+
This is hardcoded for platformname to always be self.platformname
192+
"""
193+
platforms = self.tls.getTestPlanPlatforms(self.testplanid)
194+
for platform in platforms:
195+
if platform['name'] == platformname:
196+
return platform['id']
197+
# Platformname houses platform creation as platform creation w/o a name isn't possible
198+
if not self.platformname:
199+
raise RuntimeError(
200+
"Couldn't find platformid for {}.{}, "
201+
"please provide a platformname to generate.".format(self.testplanid, platformname)
202+
)
203+
if _firstrun is True:
204+
return self.getPlatformID(self.platformname, _firstrun=False)
205+
else:
206+
raise RuntimeError("PlatformID not found after generated from platformname '{}' "
207+
"in test plan {}.".format(self.platformname, self.testplanid))
208+
209+
210+
class AddBuildReporter(TestReporter):
211+
@property
212+
def buildid(self):
213+
bid = self.get('buildid')
214+
if not bid or bid not in self.tls.getBuildsForTestPlan(self.testplanid):
215+
self['buildid'] = self._generate_buildid()
216+
return self.get('buildid')
217+
218+
def _generate_buildid(self):
219+
r = self.tls.createBuild(self.testplanid, self.buildname, self.buildnotes)
220+
return r[0]['id']
221+
222+
223+
class TestGenReporter(AddTestReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter):
224+
"""This is the default generate everything it can version of test reporting.
225+
226+
If you don't want to generate one of these values you can 'roll your own' version of this class with only the
227+
needed features that you want to generate.
228+
229+
For example if you wanted to add platforms and/or tests to testplans, but didn't want to ever make a new testplan
230+
you could use a class like:
231+
`type('MyOrgTestGenReporter', (AddTestReporter, AddPlatformReporter, TestReporter), {})`
232+
233+
Example usage with fake testlink server test and a manual project.
234+
```
235+
tls = testlink.TestLinkHelper('https://testlink.corp.com/testlink/lib/api/xmlrpc/v1/xmlrpc.php',
236+
'devkeyabc123').connect(testlink.TestlinkAPIClient)
237+
tgr = TestGenReporter(tls, ['TEST-123'], testprojectname='MANUALLY_MADE_PROJECT', testplanname='generated',
238+
platformname='gend', buildname='8.fake', status='p')
239+
```
240+
"""

Diff for: src/testlink/version.py

-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,3 @@
1919

2020
VERSION = '0.6.4'
2121
TL_RELEASE = '1.9.16'
22-

0 commit comments

Comments
 (0)