Skip to content

Commit 7ac681d

Browse files
author
Luiko Czub
committed
whatArgs(apiMethodName) implemented lczub#8
1 parent cd3ed27 commit 7ac681d

8 files changed

+194
-18
lines changed

Diff for: example/TestLinkExample.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,21 @@
8888
NEWTESTSUITE_AA="AA - Second Level"
8989
NEWTESTCASE_AA="TESTCASE_AA"
9090
NEWTESTCASE_B="TESTCASE_B"
91-
NEWBUILD="Build v0.4.5"
91+
NEWBUILD='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
9292

9393
NEWATTACHMENT_PY= os.path.realpath(__file__)
9494
this_file_dirname=os.path.dirname(NEWATTACHMENT_PY)
9595
NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
9696

97+
# example asking the api client about methods arguments
98+
print myTestLink.whatArgs('createTestCase')
99+
100+
97101
# -- Start CHANGE v0.4.5 --
98102
# if myTestLink.checkDevKey() != True:
99103
# print "Error with the devKey."
100104
# sys.exit(-1)
105+
101106
# example handling Response Error Codes
102107
# first check an invalid devKey and than the own one
103108
try:

Diff for: example/TestLinkExampleGenericApi.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,16 @@
7878
NEWTESTSUITE_AA="AA - Second Level"
7979
NEWTESTCASE_AA="TESTCASE_AA"
8080
NEWTESTCASE_B="TESTCASE_B"
81-
NEWBUILD="Build v0.4.5"
81+
NEWBUILD='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
8282

8383
NEWATTACHMENT_PY= os.path.realpath(__file__)
8484
this_file_dirname=os.path.dirname(NEWATTACHMENT_PY)
8585
NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
8686

87+
# example asking the api client about methods arguments
88+
print myTestLink.whatArgs('createTestCase')
89+
90+
8791
# example handling Response Error Codes
8892
# first check an invalid devKey and than the own one
8993
try:

Diff for: src/testlink/testlinkapigeneric.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import xmlrpclib
2121
import testlinkerrors
2222
from .testlinkhelper import TestLinkHelper, VERSION
23-
from .testlinkargs import getMethodsWithPositionalArgs
23+
from .testlinkargs import getMethodsWithPositionalArgs, getArgsForMethod
2424
from .testlinkdecorators import decoApiCallAddAttachment,\
2525
decoApiCallAddDevKey, decoApiCallWithoutArgs, \
2626
decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs
@@ -1086,6 +1086,65 @@ def _checkResponse(self, response, methodNameAPI, argsOptional):
10861086
# ADDITIONNAL FUNCTIONS
10871087
#
10881088

1089+
def whatArgs(self, methodNameAPI):
1090+
""" returns for METHODNAME a description with
1091+
- positional, optional and other (non api) mandatory args
1092+
- methods doc/help string
1093+
"""
1094+
1095+
# collect arg names
1096+
posArgNames = self._positionalArgNames.get(methodNameAPI, [])
1097+
otherArgs = ([],[])
1098+
registeredApiMethod = True
1099+
try:
1100+
otherArgs = getArgsForMethod(methodNameAPI, posArgNames)
1101+
except testlinkerrors.TLArgError:
1102+
# no API args registered for methodName
1103+
registeredApiMethod = False
1104+
optArgNames=otherArgs[0]
1105+
manArgNames=otherArgs[1]
1106+
1107+
# get method doc string
1108+
ownMethod = True
1109+
docString = None
1110+
argSeparator = ''
1111+
try:
1112+
apiMethod = self.__getattribute__(methodNameAPI)
1113+
docString = apiMethod.__doc__
1114+
except AttributeError:
1115+
# no real method defined for methodNameAPI
1116+
ownMethod = False
1117+
1118+
# now we start to build the description
1119+
# first the method name
1120+
methDescr = ''
1121+
if not ownMethod:
1122+
methDescr = "callServerWithPosArgs('%s'" % methodNameAPI
1123+
argSeparator = ', '
1124+
if not optArgNames:
1125+
optArgNames = ['apiArg']
1126+
else:
1127+
methDescr = "%s(" % methodNameAPI
1128+
1129+
# description pos and mandatory args
1130+
manArgNames.extend(posArgNames)
1131+
if manArgNames:
1132+
tmp_l = ['<%s>' % x for x in manArgNames]
1133+
methDescr += '%s%s' % (argSeparator, ", ".join(tmp_l))
1134+
argSeparator = ', '
1135+
# description optional args
1136+
if optArgNames:
1137+
tmp_l = ['%s=<%s>' % (x,x) for x in optArgNames]
1138+
methDescr += '%s[%s]' % (argSeparator, "], [".join(tmp_l))
1139+
1140+
# closing the method call
1141+
methDescr += ")"
1142+
1143+
# now append methods docstring
1144+
if docString:
1145+
methDescr += "\n%s" % docString
1146+
1147+
return methDescr
10891148

10901149
def __str__(self):
10911150
message = """

Diff for: src/testlink/testlinkargs.py

+32-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#
3030
# definitions structure is
3131
# key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs],
32-
# [other mandatory Args] )
32+
# [other mandatory non api Args] )
3333
# [all apiArgs] includes all positional and optional args without other
3434
# mandatory Args
3535
_apiMethodsArgs = {}
@@ -53,12 +53,10 @@ def registerMethod(methodName, apiArgsPositional=[], apiArgsOptional=[],
5353
5454
definitions structure is
5555
key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs],
56-
[other mandatory Args] )
56+
[other mandatory non api Args] )
5757
[all apiArgs] includes all positional and optional args without other
5858
mandatory Args """
5959

60-
print 'register args for', methodName
61-
6260
if methodName in _apiMethodsArgs:
6361
raise TLArgError('apiMethod %s already registered!' % methodName)
6462

@@ -76,6 +74,13 @@ def registerArgOptional(methodName, argName):
7674
if not argName in allArgs:
7775
allArgs.append(argName)
7876

77+
def registerArgNonApi(methodName, argName):
78+
""" Update _apiMethodsArgs[methodName] with additional non api argument """
79+
80+
nonApiArgs = _getMethodsArgDefinition(methodName)[2]
81+
if not argName in nonApiArgs:
82+
nonApiArgs.append(argName)
83+
7984

8085
def getMethodsWithPositionalArgs():
8186
""" returns a dictionary with method names and there positional args """
@@ -86,6 +91,26 @@ def getMethodsWithPositionalArgs():
8691
positionalArgNames[mname] = argdef[0][:]
8792
return positionalArgNames
8893

89-
def getApiArgsForMethod(methodName):
90-
""" returns list with all argument name api methodName """
91-
return _getMethodsArgDefinition(methodName)[1][:]
94+
# def getApiArgsForMethod(methodName):
95+
# """ returns list with all api argument name for METHODNAME """
96+
# return _getMethodsArgDefinition(methodName)[1][:]
97+
98+
def getArgsForMethod(methodName, knownArgNames=[]):
99+
""" returns for METHODNAME additional arg names as a tuple with two lists
100+
a) optional api arguments, not listed in knownArgNames
101+
b) additional mandatory non api arguments
102+
103+
raise TLArgError, if METHODNAME is not registered """
104+
105+
# argument definitions in _apiMethodsArgs
106+
argDef = _getMethodsArgDefinition(methodName)
107+
108+
# find missing optional arg names
109+
apiArgsAll = argDef[1]
110+
apiArgs = [x for x in apiArgsAll if x not in knownArgNames]
111+
112+
# other mandatory arg names
113+
manArgs = argDef[2][:]
114+
115+
return (apiArgs, manArgs)
116+

Diff for: src/testlink/testlinkdecorators.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# ------------------------------------------------------------------------
1919

2020
from functools import wraps
21-
from .testlinkargs import registerMethod, registerArgOptional
21+
from .testlinkargs import registerMethod, registerArgOptional, registerArgNonApi
2222
from .testlinkerrors import TLResponseError
2323

2424
__doc__ = """ This internal module defines the decorator functions, which are
@@ -144,6 +144,8 @@ def decoApiCallAddAttachment(methodAPI):
144144
""" Decorator to expand parameter list with devKey and attachmentfile
145145
attachmentfile is a python file descriptor pointing to the file
146146
"""
147+
registerArgOptional(methodAPI.__name__, 'devKey')
148+
registerArgNonApi(methodAPI.__name__, 'attachmentfile')
147149
@wraps(methodAPI)
148150
def wrapperAddAttachment(self, attachmentfile, *argsPositional, **argsOptional):
149151
if not ('devKey' in argsOptional):

Diff for: test/utest/testlinkapigeneric_offline_test.py

+24
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,28 @@ def test_checkResponse_dictionaryResponse(self):
187187
self.api._checkResponse(response, 'DummyMethod',
188188
{'Uno' : 1, 'due' :2, 'tre' : 3})
189189

190+
def test_whatArgs_noArgs(self):
191+
response = self.api.whatArgs('sayHello')
192+
self.assertRegexpMatches(response, 'sayHello().*')
193+
194+
def test_whatArgs_onlyOptionalArgs(self):
195+
response = self.api.whatArgs('getTestCaseAttachments')
196+
self.assertRegexpMatches(response, 'getTestCaseAttachments\(\[.*=<.*>\].*\).*')
197+
198+
def test_whatArgs_OptionalAndPositionalArgs(self):
199+
response = self.api.whatArgs('createBuild')
200+
self.assertRegexpMatches(response, 'createBuild\(<.*>.*\).*')
201+
202+
def test_whatArgs_MandatoryArgs(self):
203+
response = self.api.whatArgs('uploadExecutionAttachment')
204+
self.assertRegexpMatches(response,
205+
'uploadExecutionAttachment\(<attachmentfile>, <.*>.*\).*')
206+
207+
def test_whatArgs_unknownMethods(self):
208+
response = self.api.whatArgs('apiUnknown')
209+
self.assertRegexpMatches(response,
210+
"callServerWithPosArgs\('apiUnknown', \[apiArg=<apiArg>\]\)")
211+
190212
def test_noWrapperName_apiMethods(self):
191213
" decorator test: API Methods internal function name should be unchanged "
192214

@@ -198,6 +220,8 @@ def test_noWrapperName_apiMethods(self):
198220
self.assertEqual('createBuild', self.api.createBuild.__name__)
199221
# apiMethod with decorator @decoMakerApiCallReplaceTLResponseError()
200222
self.assertEqual('getProjectTestPlans', self.api.getProjectTestPlans.__name__)
223+
# apiMethod with decorator @decoApiCallAddAttachment
224+
self.assertEqual('uploadExecutionAttachment', self.api.uploadExecutionAttachment.__name__)
201225

202226
def test_ping(self):
203227
self.api.loadScenario(SCENARIO_A)

Diff for: test/utest/testlinkargstest.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ def setUp(self):
3636
self.mut = testlinkargs
3737
# reset the args cache
3838
self.mut._resetRegister()
39+
# api simulation
40+
self.api = self
3941

42+
def tearDown(self):
43+
# reset the args cache
44+
self.mut._resetRegister()
4045

4146
def test__resetRegister(self):
4247
self.mut._apiMethodsArgs['BigBird'] = 'not a Small Bird'
@@ -97,11 +102,39 @@ def test_registerArgOptional_ErrorUnknownMethod(self):
97102
'DummyMethod not registered'):
98103
self.mut.registerArgOptional('DummyMethod', 'sei')
99104

100-
def test_getApiArgsForMethod(self):
105+
def test_registerArgNonApi(self):
106+
self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'],
107+
['quad','tre'], ['cinque'])
108+
self.mut.registerArgNonApi('DummyMethod', 'sei')
109+
a_def = self.mut._apiMethodsArgs['DummyMethod']
110+
self.assertEqual((['Uno', 'due', 'tre'],
111+
['Uno', 'due', 'tre', 'quad'],
112+
['cinque', 'sei']), a_def )
113+
114+
def test_getArgsForMethod_onlyOptionalArgs(self):
115+
self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'],
116+
['quad','tre'])
117+
response = self.mut.getArgsForMethod('DummyMethod')
118+
self.assertEqual(response, (['Uno', 'due', 'tre', 'quad'], []) )
119+
120+
def test_getArgsForMethod_OptionalAndPositionalArgs(self):
121+
self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'],
122+
['quad','tre'])
123+
response = self.mut.getArgsForMethod('DummyMethod', ['Uno', 'quad'])
124+
self.assertEqual(response, (['due', 'tre'], []) )
125+
126+
def test_getArgsForMethod_nonApiArgs(self):
101127
self.mut.registerMethod('DummyMethod', ['Uno', 'due', 'tre'],
102128
['quad','tre'], ['cinque'])
103-
allArgs = self.mut.getApiArgsForMethod('DummyMethod')
104-
self.assertEqual(['Uno', 'due', 'tre', 'quad'], allArgs )
129+
response = self.mut.getArgsForMethod('DummyMethod',
130+
['Uno', 'due', 'tre'])
131+
self.assertEqual(response, (['quad'], ['cinque']) )
132+
133+
def test_getArgsForMethod_unknownMethods(self):
134+
with self.assertRaisesRegexp(testlinkargs.TLArgError,
135+
'unknownMethod not registered'):
136+
self.mut.getArgsForMethod('unknownMethod')
137+
105138

106139
if __name__ == "__main__":
107140
#import sys;sys.argv = ['', 'Test.testName']

Diff for: test/utest/testlinkdecoratorstest.py

+28-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import unittest
2424
from testlink.testlinkerrors import TLResponseError
25-
from testlink.testlinkargs import registerMethod, getApiArgsForMethod
25+
from testlink.testlinkargs import registerMethod, getArgsForMethod
2626
from testlink.testlinkdecorators import decoApiCallAddAttachment,\
2727
decoApiCallAddDevKey, decoApiCallWithoutArgs, \
2828
decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs
@@ -34,6 +34,13 @@ class testlinkdecoratorsTestCase(unittest.TestCase):
3434
devKey = '007'
3535
def setUp(self):
3636
self.api = self
37+
38+
def _getAttachmentArgs(self, attachmentfile):
39+
# simulation of TestlinkAPIGeneric._getAttachmentArgs()
40+
# needed in test_decoApiCallAddAttachment
41+
return {'filename': 'name %s' % attachmentfile,
42+
'filetype': 'type %s' % attachmentfile,
43+
'content' : 'content %s' % attachmentfile}
3744

3845
# def tearDown(self):
3946
# pass
@@ -60,7 +67,6 @@ def DummyMethod(a_api):
6067
pass
6168

6269
posArgs = getMethodsWithPositionalArgs()
63-
print posArgs
6470
self.assertEqual(['Uno', 'due', 'tre'], posArgs['DummyMethod'])
6571

6672
def test_noWrapperName_decoApiCallWithArgs(self):
@@ -84,8 +90,8 @@ def a_func(a_api, *argsPositional, **argsOptional):
8490
return argsPositional, argsOptional
8591

8692
# check method argument definition
87-
allArgs = getApiArgsForMethod('a_func')
88-
self.assertEqual(['devKey'], allArgs)
93+
allArgs = getArgsForMethod('a_func')
94+
self.assertEqual((['devKey'], []), allArgs)
8995
# check call arguments
9096
response = a_func(self.api)
9197
self.assertEqual({'devKey' : self.api.devKey}, response[1])
@@ -158,9 +164,27 @@ def orig_funcname4(a_api, *argsPositional, **argsOptional):
158164
self.assertEqual('orig doc string', orig_funcname4.__doc__)
159165
self.assertEqual('testlinkdecoratorstest', orig_funcname4.__module__)
160166

167+
def test_decoApiCallAddAttachment(self):
168+
" decorator test: argsOptional should be extended attachment file infos"
169+
170+
registerMethod('func_addAttachment')
171+
@decoApiCallAddAttachment
172+
def func_addAttachment(a_api, *argsPositional, **argsOptional):
173+
return argsPositional, argsOptional
174+
175+
# check method argument definition
176+
allArgs = getArgsForMethod('func_addAttachment')
177+
self.assertEqual((['devKey'], ['attachmentfile']), allArgs)
178+
# check call arguments
179+
response = func_addAttachment(self.api, 'a_file')
180+
self.assertEqual({'devKey' : self.api.devKey, 'filename': 'name a_file',
181+
'filetype': 'type a_file', 'content' : 'content a_file'},
182+
response[1])
183+
161184
def test_noWrapperName_decoApiCallAddAttachment(self):
162185
" decorator test: original function name should be unchanged "
163186

187+
registerMethod('orig_funcname5')
164188
@decoApiCallAddAttachment
165189
def orig_funcname5(a_api):
166190
"orig doc string"

0 commit comments

Comments
 (0)