1 |
fvlingen |
1.1 |
#!/usr/bin/env python
|
2 |
metson |
1.21 |
from distutils.core import setup, Command
|
3 |
|
|
from unittest import TextTestRunner, TestLoader, TestSuite
|
4 |
|
|
from glob import glob
|
5 |
|
|
from os.path import splitext, basename, join as pjoin, walk
|
6 |
metson |
1.85 |
from ConfigParser import ConfigParser, NoOptionError
|
7 |
meloam |
1.53 |
import os, sys, os.path
|
8 |
metson |
1.85 |
import logging
|
9 |
|
|
import unittest
|
10 |
metson |
1.51 |
#PyLinter and coverage aren't standard, but aren't strictly necessary
|
11 |
|
|
can_lint = False
|
12 |
|
|
can_coverage = False
|
13 |
meloam |
1.88 |
can_nose = False
|
14 |
sfoulkes |
1.29 |
try:
|
15 |
metson |
1.87 |
from pylint.lint import Run
|
16 |
metson |
1.85 |
from pylint.lint import preprocess_options, cb_init_hook
|
17 |
|
|
from pylint import checkers
|
18 |
metson |
1.51 |
can_lint = True
|
19 |
|
|
except:
|
20 |
|
|
pass
|
21 |
|
|
try:
|
22 |
meloam |
1.47 |
import coverage
|
23 |
metson |
1.51 |
can_coverage = True
|
24 |
sfoulkes |
1.29 |
except:
|
25 |
|
|
pass
|
26 |
fvlingen |
1.1 |
|
27 |
meloam |
1.88 |
try:
|
28 |
|
|
import nose
|
29 |
|
|
can_nose = True
|
30 |
|
|
except:
|
31 |
|
|
pass
|
32 |
|
|
|
33 |
|
|
if can_nose:
|
34 |
|
|
class NoseCommand(Command):
|
35 |
|
|
user_options = [ ]
|
36 |
|
|
|
37 |
|
|
def initialize_options(self):
|
38 |
|
|
pass
|
39 |
|
|
|
40 |
|
|
def finalize_options(self):
|
41 |
|
|
pass
|
42 |
|
|
|
43 |
|
|
def run(self):
|
44 |
meloam |
1.90 |
retval = nose.run(argv=[__file__,'--all-modules','-v','test/python'])
|
45 |
|
|
print "nose returned: %s" % retval
|
46 |
meloam |
1.91 |
return retval
|
47 |
meloam |
1.88 |
else:
|
48 |
|
|
class NoseCommand(Command):
|
49 |
|
|
user_options = [ ]
|
50 |
|
|
def run(self):
|
51 |
|
|
print "Nose isn't installed, fail"
|
52 |
|
|
pass
|
53 |
|
|
|
54 |
|
|
def initialize_options(self):
|
55 |
|
|
pass
|
56 |
|
|
|
57 |
|
|
def finalize_options(self):
|
58 |
|
|
pass
|
59 |
|
|
pass
|
60 |
|
|
|
61 |
metson |
1.87 |
if can_lint:
|
62 |
|
|
class LinterRun(Run):
|
63 |
|
|
def __init__(self, args, reporter=None):
|
64 |
|
|
self._rcfile = None
|
65 |
|
|
self._plugins = []
|
66 |
|
|
preprocess_options(args, {
|
67 |
|
|
# option: (callback, takearg)
|
68 |
|
|
'rcfile': (self.cb_set_rcfile, True),
|
69 |
|
|
'load-plugins': (self.cb_add_plugins, True),
|
70 |
|
|
})
|
71 |
|
|
self.linter = linter = self.LinterClass((
|
72 |
|
|
('rcfile',
|
73 |
|
|
{'action' : 'callback', 'callback' : lambda *args: 1,
|
74 |
|
|
'type': 'string', 'metavar': '<file>',
|
75 |
|
|
'help' : 'Specify a configuration file.'}),
|
76 |
|
|
|
77 |
|
|
('init-hook',
|
78 |
|
|
{'action' : 'callback', 'type' : 'string', 'metavar': '<code>',
|
79 |
|
|
'callback' : cb_init_hook,
|
80 |
|
|
'help' : 'Python code to execute, usually for sys.path \
|
81 |
|
|
manipulation such as pygtk.require().'}),
|
82 |
|
|
|
83 |
|
|
('help-msg',
|
84 |
|
|
{'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
|
85 |
|
|
'callback' : self.cb_help_message,
|
86 |
|
|
'group': 'Commands',
|
87 |
|
|
'help' : '''Display a help message for the given message id and \
|
88 |
|
|
exit. The value may be a comma separated list of message ids.'''}),
|
89 |
|
|
|
90 |
|
|
('list-msgs',
|
91 |
|
|
{'action' : 'callback', 'metavar': '<msg-id>',
|
92 |
|
|
'callback' : self.cb_list_messages,
|
93 |
|
|
'group': 'Commands',
|
94 |
|
|
'help' : "Generate pylint's full documentation."}),
|
95 |
|
|
|
96 |
|
|
('generate-rcfile',
|
97 |
|
|
{'action' : 'callback', 'callback' : self.cb_generate_config,
|
98 |
|
|
'group': 'Commands',
|
99 |
|
|
'help' : '''Generate a sample configuration file according to \
|
100 |
|
|
the current configuration. You can put other options before this one to get \
|
101 |
|
|
them in the generated configuration.'''}),
|
102 |
|
|
|
103 |
|
|
('generate-man',
|
104 |
|
|
{'action' : 'callback', 'callback' : self.cb_generate_manpage,
|
105 |
|
|
'group': 'Commands',
|
106 |
|
|
'help' : "Generate pylint's man page.",'hide': 'True'}),
|
107 |
|
|
|
108 |
|
|
('errors-only',
|
109 |
|
|
{'action' : 'callback', 'callback' : self.cb_error_mode,
|
110 |
|
|
'short': 'e',
|
111 |
|
|
'help' : '''In error mode, checkers without error messages are \
|
112 |
|
|
disabled and for others, only the ERROR messages are displayed, and no reports \
|
113 |
|
|
are done by default'''}),
|
114 |
|
|
|
115 |
|
|
('profile',
|
116 |
|
|
{'type' : 'yn', 'metavar' : '<y_or_n>',
|
117 |
|
|
'default': False,
|
118 |
|
|
'help' : 'Profiled execution.'}),
|
119 |
|
|
|
120 |
|
|
), option_groups=self.option_groups,
|
121 |
|
|
reporter=reporter, pylintrc=self._rcfile)
|
122 |
|
|
# register standard checkers
|
123 |
|
|
checkers.initialize(linter)
|
124 |
|
|
# load command line plugins
|
125 |
|
|
linter.load_plugin_modules(self._plugins)
|
126 |
|
|
# read configuration
|
127 |
|
|
linter.disable_message('W0704')
|
128 |
|
|
linter.read_config_file()
|
129 |
|
|
# is there some additional plugins in the file configuration, in
|
130 |
|
|
config_parser = linter._config_parser
|
131 |
|
|
if config_parser.has_option('MASTER', 'load-plugins'):
|
132 |
|
|
plugins = splitstrip(config_parser.get('MASTER', 'load-plugins'))
|
133 |
|
|
linter.load_plugin_modules(plugins)
|
134 |
|
|
# now we can load file config and command line, plugins (which can
|
135 |
|
|
# provide options) have been registered
|
136 |
|
|
linter.load_config_file()
|
137 |
|
|
if reporter:
|
138 |
|
|
# if a custom reporter is provided as argument, it may be overriden
|
139 |
|
|
# by file parameters, so re-set it here, but before command line
|
140 |
|
|
# parsing so it's still overrideable by command line option
|
141 |
|
|
linter.set_reporter(reporter)
|
142 |
|
|
args = linter.load_command_line_configuration(args)
|
143 |
|
|
# insert current working directory to the python path to have a correct
|
144 |
|
|
# behaviour
|
145 |
|
|
sys.path.insert(0, os.getcwd())
|
146 |
|
|
if self.linter.config.profile:
|
147 |
|
|
print >> sys.stderr, '** profiled run'
|
148 |
|
|
from hotshot import Profile, stats
|
149 |
|
|
prof = Profile('stones.prof')
|
150 |
|
|
prof.runcall(linter.check, args)
|
151 |
|
|
prof.close()
|
152 |
|
|
data = stats.load('stones.prof')
|
153 |
|
|
data.strip_dirs()
|
154 |
|
|
data.sort_stats('time', 'calls')
|
155 |
|
|
data.print_stats(30)
|
156 |
|
|
sys.path.pop(0)
|
157 |
|
|
|
158 |
|
|
def cb_set_rcfile(self, name, value):
|
159 |
|
|
"""callback for option preprocessing (ie before optik parsing)"""
|
160 |
|
|
self._rcfile = value
|
161 |
|
|
|
162 |
|
|
def cb_add_plugins(self, name, value):
|
163 |
|
|
"""callback for option preprocessing (ie before optik parsing)"""
|
164 |
|
|
self._plugins.extend(splitstrip(value))
|
165 |
|
|
|
166 |
|
|
def cb_error_mode(self, *args, **kwargs):
|
167 |
|
|
"""error mode:
|
168 |
|
|
* checkers without error messages are disabled
|
169 |
|
|
* for others, only the ERROR messages are displayed
|
170 |
|
|
* disable reports
|
171 |
|
|
* do not save execution information
|
172 |
|
|
"""
|
173 |
|
|
self.linter.disable_noerror_checkers()
|
174 |
|
|
self.linter.set_option('disable-msg-cat', 'WCRFI')
|
175 |
|
|
self.linter.set_option('reports', False)
|
176 |
|
|
self.linter.set_option('persistent', False)
|
177 |
|
|
|
178 |
|
|
def cb_generate_config(self, *args, **kwargs):
|
179 |
|
|
"""optik callback for sample config file generation"""
|
180 |
|
|
self.linter.generate_config(skipsections=('COMMANDS',))
|
181 |
|
|
|
182 |
|
|
def cb_generate_manpage(self, *args, **kwargs):
|
183 |
|
|
"""optik callback for sample config file generation"""
|
184 |
|
|
from pylint import __pkginfo__
|
185 |
|
|
self.linter.generate_manpage(__pkginfo__)
|
186 |
|
|
|
187 |
|
|
def cb_help_message(self, option, opt_name, value, parser):
|
188 |
|
|
"""optik callback for printing some help about a particular message"""
|
189 |
|
|
self.linter.help_message(splitstrip(value))
|
190 |
|
|
|
191 |
|
|
def cb_list_messages(self, option, opt_name, value, parser):
|
192 |
|
|
"""optik callback for printing available messages"""
|
193 |
|
|
self.linter.list_messages()
|
194 |
|
|
else:
|
195 |
|
|
class LinterRun:
|
196 |
|
|
def __init__(self):
|
197 |
|
|
pass
|
198 |
metson |
1.85 |
|
199 |
metson |
1.22 |
"""
|
200 |
|
|
Build, clean and test the WMCore package.
|
201 |
|
|
"""
|
202 |
|
|
|
203 |
metson |
1.92 |
def generate_filelist(basepath=None, recurse=True, ignore=False):
|
204 |
metson |
1.85 |
if basepath:
|
205 |
|
|
walkpath = os.path.join(get_relative_path(), 'src/python', basepath)
|
206 |
|
|
else:
|
207 |
|
|
walkpath = os.path.join(get_relative_path(), 'src/python')
|
208 |
|
|
|
209 |
metson |
1.36 |
files = []
|
210 |
metson |
1.85 |
|
211 |
|
|
if walkpath.endswith('.py'):
|
212 |
metson |
1.92 |
if ignore and walkpath.endswith(ignore):
|
213 |
|
|
files.append(walkpath)
|
214 |
metson |
1.85 |
else:
|
215 |
|
|
for dirpath, dirnames, filenames in os.walk(walkpath):
|
216 |
|
|
# skipping CVS directories and their contents
|
217 |
|
|
pathelements = dirpath.split('/')
|
218 |
|
|
result = []
|
219 |
|
|
if not 'CVS' in pathelements:
|
220 |
|
|
# to build up a list of file names which contain tests
|
221 |
|
|
for file in filenames:
|
222 |
|
|
if file.endswith('.py'):
|
223 |
|
|
filepath = '/'.join([dirpath, file])
|
224 |
|
|
files.append(filepath)
|
225 |
|
|
|
226 |
|
|
if len(files) == 0 and recurse:
|
227 |
|
|
files = generate_filelist(basepath + '.py', not recurse)
|
228 |
|
|
|
229 |
metson |
1.36 |
return files
|
230 |
|
|
|
231 |
metson |
1.85 |
def lint_score(stats, evaluation):
|
232 |
|
|
return eval(evaluation, {}, stats)
|
233 |
|
|
|
234 |
|
|
def lint_files(files, reports=False):
|
235 |
metson |
1.36 |
"""
|
236 |
metson |
1.85 |
lint a (list of) file(s) and return the results as a dictionary containing
|
237 |
|
|
filename : result_dict
|
238 |
metson |
1.36 |
"""
|
239 |
metson |
1.85 |
|
240 |
|
|
rcfile=os.path.join(get_relative_path(),'standards/.pylintrc')
|
241 |
|
|
|
242 |
metson |
1.92 |
arguements = ['--rcfile=%s' % rcfile, '--ignore=DefaultConfig.py']
|
243 |
metson |
1.86 |
|
244 |
|
|
if not reports:
|
245 |
|
|
arguements.append('-rn')
|
246 |
|
|
|
247 |
metson |
1.85 |
arguements.extend(files)
|
248 |
|
|
|
249 |
|
|
lntr = LinterRun(arguements)
|
250 |
|
|
|
251 |
|
|
results = {}
|
252 |
|
|
for file in files:
|
253 |
|
|
lntr.linter.check(file)
|
254 |
|
|
results[file] = {'stats': lntr.linter.stats,
|
255 |
|
|
'score': lint_score(lntr.linter.stats,
|
256 |
|
|
lntr.linter.config.evaluation)
|
257 |
|
|
}
|
258 |
|
|
if reports:
|
259 |
|
|
print '----------------------------------'
|
260 |
|
|
print 'Your code has been rated at %.2f/10' % \
|
261 |
|
|
lint_score(lntr.linter.stats, lntr.linter.config.evaluation)
|
262 |
metson |
1.36 |
|
263 |
metson |
1.85 |
return results, lntr.linter.config.evaluation
|
264 |
meloam |
1.69 |
|
265 |
|
|
|
266 |
|
|
|
267 |
meloam |
1.70 |
MODULE_EXTENSIONS = set('.py'.split())
|
268 |
|
|
## bad bad bad global variable, FIXME
|
269 |
|
|
all_test_suites = []
|
270 |
meloam |
1.69 |
|
271 |
meloam |
1.72 |
|
272 |
meloam |
1.69 |
def get_test_suites(path):
|
273 |
|
|
""":return: Iterable of suites for the packages/modules
|
274 |
|
|
present under :param:`path`.
|
275 |
|
|
"""
|
276 |
meloam |
1.70 |
logging.info('Base path: %s', path)
|
277 |
meloam |
1.69 |
suites = []
|
278 |
meloam |
1.70 |
os.path.walk(path, unit_test_extractor, (path, suites))
|
279 |
|
|
logging.info('Got suites: %s', all_test_suites)
|
280 |
|
|
return all_test_suites
|
281 |
meloam |
1.69 |
|
282 |
meloam |
1.76 |
import re
|
283 |
|
|
def listFiles(dir):
|
284 |
|
|
fileList = []
|
285 |
|
|
basedir = dir
|
286 |
|
|
for item in os.listdir(dir):
|
287 |
|
|
if os.path.isfile(os.path.join(basedir,item)):
|
288 |
|
|
fileList.append(os.path.join(basedir,item))
|
289 |
|
|
elif os.path.isdir(os.path.join(basedir,item)):
|
290 |
|
|
fileList.extend(listFiles(os.path.join(basedir,item)))
|
291 |
|
|
|
292 |
|
|
return fileList
|
293 |
|
|
|
294 |
|
|
|
295 |
|
|
import pprint
|
296 |
meloam |
1.69 |
|
297 |
meloam |
1.47 |
def runUnitTests():
|
298 |
|
|
# runs all the unittests, returning the testresults object
|
299 |
|
|
testfiles = []
|
300 |
|
|
# Add the test and src directory to the python path
|
301 |
|
|
mydir = os.getcwd()
|
302 |
|
|
# todo: not portable
|
303 |
meloam |
1.57 |
testspypath = '/'.join([mydir, 'test/python/'])
|
304 |
|
|
srcpypath = '/'.join([mydir, 'src/python/'])
|
305 |
meloam |
1.47 |
sys.path.append(testspypath)
|
306 |
|
|
sys.path.append(srcpypath)
|
307 |
meloam |
1.70 |
logging.basicConfig(level=logging.DEBUG)
|
308 |
meloam |
1.76 |
path = os.path.abspath(os.path.dirname(sys.argv[0]))
|
309 |
|
|
files = listFiles(path)
|
310 |
|
|
test = re.compile("_t\.py$", re.IGNORECASE)
|
311 |
|
|
files = filter(test.search, files)
|
312 |
|
|
filenameToModuleName = lambda f: os.path.splitext(f)[0]
|
313 |
|
|
moduleNames = map(filenameToModuleName, files)
|
314 |
|
|
stripBeginning = lambda f: f[ len(path) + len('/test/python/'): ]
|
315 |
|
|
moduleNames2 = map( stripBeginning, moduleNames )
|
316 |
|
|
replaceSlashes = lambda f: f.replace('/','.')
|
317 |
|
|
moduleNames3 = map( replaceSlashes, moduleNames2 )
|
318 |
meloam |
1.80 |
modules = []
|
319 |
|
|
loadFail = []
|
320 |
meloam |
1.76 |
for oneModule in moduleNames3:
|
321 |
|
|
try:
|
322 |
meloam |
1.78 |
__import__(oneModule)
|
323 |
|
|
modules.append(sys.modules[oneModule])
|
324 |
meloam |
1.79 |
except Exception, e:
|
325 |
meloam |
1.84 |
sys.stderr.write("ERROR: Can't load test case %s - %s\n" % (oneModule, e))
|
326 |
meloam |
1.80 |
loadFail.append(oneModule)
|
327 |
|
|
|
328 |
meloam |
1.76 |
load = unittest.defaultTestLoader.loadTestsFromModule
|
329 |
|
|
globalSuite = unittest.TestSuite(map(load, modules))
|
330 |
|
|
# # logging.basicConfig(level=logging.WARN)
|
331 |
|
|
# package_path = os.path.dirname(mydir + '/test/python/')
|
332 |
|
|
# print "path: %s " % package_path
|
333 |
|
|
# suites = get_test_suites(package_path)
|
334 |
|
|
# testCaseCount = 0
|
335 |
|
|
# totallySuite = unittest.TestSuite()
|
336 |
|
|
# totallySuite.addTests(suites)
|
337 |
|
|
# print suites
|
338 |
|
|
result = unittest.TextTestRunner(verbosity=2).run(globalSuite)
|
339 |
meloam |
1.70 |
|
340 |
meloam |
1.59 |
#sys.stdout = sys.__stdout__
|
341 |
|
|
#sys.stderr = sys.__stderr__
|
342 |
meloam |
1.57 |
|
343 |
|
|
print sys.path
|
344 |
meloam |
1.81 |
return (result, loadFail, globalSuite.countTestCases())
|
345 |
meloam |
1.47 |
|
346 |
metson |
1.85 |
def get_relative_path():
|
347 |
|
|
return os.path.dirname(os.path.abspath(os.path.join(os.getcwd(), sys.argv[0])))
|
348 |
|
|
|
349 |
meloam |
1.47 |
|
350 |
metson |
1.21 |
class TestCommand(Command):
|
351 |
metson |
1.49 |
description = "Handle setup.py test with this class - walk through the " + \
|
352 |
|
|
"directory structure building up a list of tests, then build a test " + \
|
353 |
|
|
" suite and execute it."
|
354 |
metson |
1.22 |
"""
|
355 |
|
|
TODO: Pull database URL's from environment, and skip tests where database
|
356 |
|
|
URL is not present (e.g. for a slave without Oracle connection)
|
357 |
meloam |
1.46 |
|
358 |
|
|
TODO: need to build a separate test runner for each test file, python is
|
359 |
|
|
keeping the test objects around, which is keeping it from destroying
|
360 |
|
|
filehandles, which is causing us to bomb out of a lot more tests than
|
361 |
|
|
necessary. Or, people could learn to close their files themselves.
|
362 |
|
|
either-or.
|
363 |
metson |
1.22 |
"""
|
364 |
metson |
1.21 |
user_options = [ ]
|
365 |
|
|
|
366 |
|
|
def initialize_options(self):
|
367 |
metson |
1.85 |
self._dir = get_relative_path()
|
368 |
metson |
1.21 |
|
369 |
|
|
def finalize_options(self):
|
370 |
|
|
pass
|
371 |
|
|
|
372 |
|
|
def run(self):
|
373 |
|
|
'''
|
374 |
metson |
1.25 |
Finds all the tests modules in test/python/WMCore_t, and runs them.
|
375 |
metson |
1.21 |
'''
|
376 |
|
|
testfiles = [ ]
|
377 |
metson |
1.26 |
|
378 |
meloam |
1.48 |
# attempt to perform coverage tests, even if they weren't asked for
|
379 |
|
|
# it doesn't cost (much), and by caching the results, a later
|
380 |
|
|
# coverage test run doesn't have to run through all the unittests
|
381 |
|
|
# just generate the coverage report
|
382 |
|
|
files = generate_filelist()
|
383 |
|
|
coverageEnabled = True;
|
384 |
|
|
try:
|
385 |
|
|
cov = coverage.coverage(branch = True, data_file="wmcore-coverage.dat" )
|
386 |
|
|
cov.start()
|
387 |
|
|
print "Caching code coverage statstics"
|
388 |
|
|
except:
|
389 |
|
|
coverageEnabled = False
|
390 |
|
|
|
391 |
meloam |
1.75 |
## FIXME: make this more portable
|
392 |
|
|
if 'WMCOREBASE' not in os.environ:
|
393 |
metson |
1.85 |
os.environ['WMCOREBASE'] = get_relative_path()
|
394 |
meloam |
1.75 |
|
395 |
meloam |
1.54 |
result, failedTestFiles, totalTests = runUnitTests()
|
396 |
meloam |
1.48 |
|
397 |
|
|
if coverageEnabled:
|
398 |
|
|
cov.stop()
|
399 |
|
|
cov.save()
|
400 |
|
|
|
401 |
meloam |
1.54 |
|
402 |
metson |
1.32 |
if not result.wasSuccessful():
|
403 |
meloam |
1.82 |
sys.stderr.write("\nTests unsuccessful. There were %s failures and %s errors\n"\
|
404 |
meloam |
1.81 |
% (len(result.failures), len(result.errors)))
|
405 |
|
|
#print "Failurelist:\n%s" % "\n".join(map(lambda x: \
|
406 |
|
|
# "FAILURE: %s\n%s" % (x[0],x[1] ), result.failures))
|
407 |
|
|
#print "Errorlist:\n%s" % "\n".join(map(lambda x: \
|
408 |
|
|
# "ERROR: %s\n%s" % (x[0],x[1] ), result.errors))
|
409 |
meloam |
1.54 |
|
410 |
|
|
if len(failedTestFiles):
|
411 |
meloam |
1.81 |
sys.stderr.write("The following tests failed to load: \n===============\n%s" %\
|
412 |
|
|
"\n".join(failedTestFiles))
|
413 |
meloam |
1.82 |
sys.stderr.write("\n------------------------------------")
|
414 |
|
|
sys.stderr.write("\ntest results")
|
415 |
|
|
sys.stderr.write("\n------------------------------------")
|
416 |
|
|
sys.stderr.write("\nStats: %s successful, %s failures, %s errors, %s didn't run" %\
|
417 |
meloam |
1.54 |
(totalTests - len(result.failures) - len(result.errors),\
|
418 |
|
|
len(result.failures),
|
419 |
|
|
len(result.errors),
|
420 |
meloam |
1.81 |
len(failedTestFiles)))
|
421 |
meloam |
1.60 |
|
422 |
meloam |
1.58 |
if (not result.wasSuccessful()) or len(result.errors):
|
423 |
meloam |
1.82 |
sys.stderr.write("\nFAILED: setup.py test\n")
|
424 |
meloam |
1.44 |
sys.exit(1)
|
425 |
|
|
else:
|
426 |
meloam |
1.82 |
sys.stderr.write("\nPASS: setup.py test\n")
|
427 |
meloam |
1.44 |
sys.exit(0)
|
428 |
meloam |
1.54 |
|
429 |
meloam |
1.44 |
|
430 |
|
|
|
431 |
|
|
|
432 |
metson |
1.21 |
class CleanCommand(Command):
|
433 |
metson |
1.49 |
description = "Clean up (delete) compiled files"
|
434 |
metson |
1.21 |
user_options = [ ]
|
435 |
|
|
|
436 |
|
|
def initialize_options(self):
|
437 |
|
|
self._clean_me = [ ]
|
438 |
|
|
for root, dirs, files in os.walk('.'):
|
439 |
|
|
for f in files:
|
440 |
|
|
if f.endswith('.pyc'):
|
441 |
|
|
self._clean_me.append(pjoin(root, f))
|
442 |
|
|
|
443 |
|
|
def finalize_options(self):
|
444 |
|
|
pass
|
445 |
|
|
|
446 |
|
|
def run(self):
|
447 |
|
|
for clean_me in self._clean_me:
|
448 |
|
|
try:
|
449 |
|
|
os.unlink(clean_me)
|
450 |
|
|
except:
|
451 |
|
|
pass
|
452 |
|
|
|
453 |
metson |
1.23 |
class LintCommand(Command):
|
454 |
metson |
1.49 |
description = "Lint all files in the src tree"
|
455 |
metson |
1.24 |
"""
|
456 |
|
|
TODO: better format the test results, get some global result, make output
|
457 |
|
|
more buildbot friendly.
|
458 |
|
|
"""
|
459 |
|
|
|
460 |
metson |
1.86 |
user_options = [ ('package=', 'p', 'package to lint, default to None'),
|
461 |
|
|
('report', 'r', 'return a detailed lint report, default False')]
|
462 |
metson |
1.23 |
|
463 |
|
|
def initialize_options(self):
|
464 |
metson |
1.85 |
self._dir = get_relative_path()
|
465 |
|
|
self.package = None
|
466 |
metson |
1.86 |
self.report = False
|
467 |
metson |
1.85 |
|
468 |
metson |
1.23 |
def finalize_options(self):
|
469 |
metson |
1.86 |
if self.report:
|
470 |
|
|
self.report = True
|
471 |
|
|
|
472 |
metson |
1.23 |
def run(self):
|
473 |
|
|
'''
|
474 |
|
|
Find the code and run lint on it
|
475 |
|
|
'''
|
476 |
metson |
1.51 |
if can_lint:
|
477 |
metson |
1.85 |
srcpypath = os.path.join(self._dir, 'src/python/')
|
478 |
|
|
|
479 |
metson |
1.51 |
sys.path.append(srcpypath)
|
480 |
|
|
|
481 |
metson |
1.85 |
files_to_lint = []
|
482 |
|
|
|
483 |
|
|
if self.package:
|
484 |
|
|
if self.package.endswith('.py'):
|
485 |
|
|
cnt = self.package.count('.') - 1
|
486 |
metson |
1.92 |
files_to_lint = generate_filelist(self.package.replace('.', '/', cnt), 'DeafultConfig.py')
|
487 |
metson |
1.85 |
else:
|
488 |
metson |
1.92 |
files_to_lint = generate_filelist(self.package.replace('.', '/'), 'DeafultConfig.py')
|
489 |
metson |
1.85 |
else:
|
490 |
metson |
1.92 |
files_to_lint = generate_filelist(ignore='DeafultConfig.py')
|
491 |
metson |
1.86 |
|
492 |
|
|
results, evaluation = lint_files(files_to_lint, self.report)
|
493 |
metson |
1.85 |
ln = len(results)
|
494 |
|
|
scr = 0
|
495 |
|
|
print
|
496 |
|
|
for k, v in results.items():
|
497 |
|
|
print "%s: %.2f/10" % (k.replace('src/python/', ''), v['score'])
|
498 |
|
|
scr += v['score']
|
499 |
|
|
if ln > 1:
|
500 |
|
|
print '--------------------------------------------------------'
|
501 |
|
|
print 'Average pylint score for %s is: %.2f/10' % (self.package,
|
502 |
|
|
scr/ln)
|
503 |
|
|
|
504 |
metson |
1.51 |
else:
|
505 |
|
|
print 'You need to install pylint before using the lint command'
|
506 |
metson |
1.36 |
|
507 |
|
|
class ReportCommand(Command):
|
508 |
metson |
1.49 |
description = "Generate a simple html report for ease of viewing in buildbot"
|
509 |
metson |
1.36 |
"""
|
510 |
|
|
To contain:
|
511 |
|
|
average lint score
|
512 |
|
|
% code coverage
|
513 |
|
|
list of classes missing tests
|
514 |
|
|
etc.
|
515 |
|
|
"""
|
516 |
|
|
|
517 |
|
|
user_options = [ ]
|
518 |
|
|
|
519 |
|
|
def initialize_options(self):
|
520 |
|
|
pass
|
521 |
|
|
|
522 |
|
|
def finalize_options(self):
|
523 |
|
|
pass
|
524 |
|
|
|
525 |
|
|
def run(self):
|
526 |
|
|
"""
|
527 |
|
|
run all the tests needed to generate the report and make an
|
528 |
|
|
html table
|
529 |
|
|
"""
|
530 |
|
|
files = generate_filelist()
|
531 |
|
|
|
532 |
|
|
error = 0
|
533 |
|
|
warning = 0
|
534 |
|
|
refactor = 0
|
535 |
|
|
convention = 0
|
536 |
|
|
statement = 0
|
537 |
|
|
|
538 |
metson |
1.85 |
srcpypath = '/'.join([get_relative_path(), 'src/python/'])
|
539 |
metson |
1.36 |
sys.path.append(srcpypath)
|
540 |
metson |
1.37 |
|
541 |
|
|
cfg = ConfigParser()
|
542 |
|
|
cfg.read('standards/.pylintrc')
|
543 |
|
|
|
544 |
metson |
1.40 |
# Supress stdout/stderr
|
545 |
metson |
1.38 |
sys.stderr = open('/dev/null', 'w')
|
546 |
metson |
1.39 |
sys.stdout = open('/dev/null', 'w')
|
547 |
meloam |
1.47 |
# wrap it in an exception handler, otherwise we can't see why it fails
|
548 |
|
|
try:
|
549 |
|
|
# lint the code
|
550 |
|
|
for stats in lint_files(files):
|
551 |
|
|
error += stats['error']
|
552 |
|
|
warning += stats['warning']
|
553 |
|
|
refactor += stats['refactor']
|
554 |
|
|
convention += stats['convention']
|
555 |
|
|
statement += stats['statement']
|
556 |
|
|
except Exception,e:
|
557 |
|
|
# and restore the stdout/stderr
|
558 |
|
|
sys.stderr = sys.__stderr__
|
559 |
|
|
sys.stdout = sys.__stderr__
|
560 |
|
|
raise e
|
561 |
metson |
1.37 |
|
562 |
metson |
1.40 |
# and restore the stdout/stderr
|
563 |
meloam |
1.47 |
sys.stderr = sys.__stderr__
|
564 |
|
|
sys.stdout = sys.__stderr__
|
565 |
metson |
1.38 |
|
566 |
metson |
1.37 |
stats = {'error': error,
|
567 |
|
|
'warning': warning,
|
568 |
|
|
'refactor': refactor,
|
569 |
|
|
'convention': convention,
|
570 |
|
|
'statement': statement}
|
571 |
|
|
|
572 |
|
|
lint_score = eval(cfg.get('MASTER', 'evaluation'), {}, stats)
|
573 |
metson |
1.36 |
coverage = 0 # TODO: calculate this
|
574 |
|
|
testless_classes = [] # TODO: generate this
|
575 |
|
|
|
576 |
|
|
print "<table>"
|
577 |
|
|
print "<tr>"
|
578 |
|
|
print "<td colspan=2><h1>WMCore test report</h1></td>"
|
579 |
|
|
print "</tr>"
|
580 |
|
|
print "<tr>"
|
581 |
|
|
print "<td>Average lint score</td>"
|
582 |
|
|
print "<td>%.2f</td>" % lint_score
|
583 |
|
|
print "</tr>"
|
584 |
|
|
print "<tr>"
|
585 |
|
|
print "<td>% code coverage</td>"
|
586 |
|
|
print "<td>%s</td>" % coverage
|
587 |
|
|
print "</tr>"
|
588 |
|
|
print "<tr>"
|
589 |
|
|
print "<td>Classes missing tests</td>"
|
590 |
|
|
print "<td>"
|
591 |
|
|
if len(testless_classes) == 0:
|
592 |
|
|
print "None"
|
593 |
|
|
else:
|
594 |
|
|
print "<ul>"
|
595 |
|
|
for c in testless_classes:
|
596 |
|
|
print "<li>%c</li>" % c
|
597 |
|
|
print "</ul>"
|
598 |
|
|
print "</td>"
|
599 |
|
|
print "</tr>"
|
600 |
|
|
print "</table>"
|
601 |
|
|
|
602 |
|
|
class CoverageCommand(Command):
|
603 |
metson |
1.49 |
description = "Run code coverage tests"
|
604 |
metson |
1.36 |
"""
|
605 |
metson |
1.49 |
To do this, we need to run all the unittests within the coverage
|
606 |
|
|
framework to record all the lines(and branches) executed
|
607 |
meloam |
1.47 |
unfortunately, we have multiple code paths per database schema, so
|
608 |
|
|
we need to find a way to merge them.
|
609 |
|
|
|
610 |
|
|
TODO: modify the test command to have a flag to record code coverage
|
611 |
|
|
the file thats used can then be used here, saving us from running
|
612 |
|
|
our tests twice
|
613 |
metson |
1.36 |
"""
|
614 |
|
|
|
615 |
|
|
user_options = [ ]
|
616 |
|
|
|
617 |
|
|
def initialize_options(self):
|
618 |
|
|
pass
|
619 |
|
|
|
620 |
|
|
def finalize_options(self):
|
621 |
|
|
pass
|
622 |
|
|
|
623 |
|
|
def run(self):
|
624 |
|
|
"""
|
625 |
|
|
Determine the code's test coverage and return that as a float
|
626 |
|
|
|
627 |
|
|
http://nedbatchelder.com/code/coverage/
|
628 |
|
|
"""
|
629 |
metson |
1.51 |
if can_coverage:
|
630 |
|
|
files = generate_filelist()
|
631 |
|
|
dataFile = None
|
632 |
|
|
cov = None
|
633 |
meloam |
1.48 |
|
634 |
metson |
1.51 |
# attempt to load previously cached coverage information if it exists
|
635 |
|
|
try:
|
636 |
|
|
dataFile = open("wmcore-coverage.dat","r")
|
637 |
|
|
cov = coverage.coverage(branch = True, data_file='wmcore-coverage.dat')
|
638 |
|
|
cov.load()
|
639 |
|
|
except:
|
640 |
|
|
cov = coverage.coverage(branch = True, )
|
641 |
|
|
cov.start()
|
642 |
|
|
runUnitTests()
|
643 |
|
|
cov.stop()
|
644 |
|
|
cov.save()
|
645 |
|
|
|
646 |
|
|
# we have our coverage information, now let's do something with it
|
647 |
|
|
# get a list of modules
|
648 |
|
|
cov.report(morfs = files, file=sys.stdout)
|
649 |
|
|
return 0
|
650 |
|
|
else:
|
651 |
|
|
print 'You need the coverage module installed before running the' +\
|
652 |
|
|
' coverage command'
|
653 |
metson |
1.36 |
|
654 |
metson |
1.41 |
class DumbCoverageCommand(Command):
|
655 |
metson |
1.49 |
description = "Run a simple coverage test - find classes that don't have a unit test"
|
656 |
metson |
1.41 |
|
657 |
|
|
user_options = [ ]
|
658 |
|
|
|
659 |
|
|
def initialize_options(self):
|
660 |
|
|
pass
|
661 |
|
|
|
662 |
|
|
def finalize_options(self):
|
663 |
|
|
pass
|
664 |
|
|
|
665 |
|
|
def run(self):
|
666 |
|
|
"""
|
667 |
|
|
Determine the code's test coverage in a dumb way and return that as a
|
668 |
|
|
float.
|
669 |
|
|
"""
|
670 |
metson |
1.42 |
print "This determines test coverage in a very crude manner. If your"
|
671 |
|
|
print "test file is incorrectly named it will not be counted, and"
|
672 |
|
|
print "result in a lower coverage score."
|
673 |
metson |
1.43 |
print '----------------------------------------------------------------'
|
674 |
metson |
1.42 |
filelist = generate_filelist()
|
675 |
|
|
tests = 0
|
676 |
|
|
files = 0
|
677 |
|
|
pkgcnt = 0
|
678 |
metson |
1.85 |
dir = get_relative_path()
|
679 |
metson |
1.42 |
pkg = {'name': '', 'files': 0, 'tests': 0}
|
680 |
|
|
for f in filelist:
|
681 |
metson |
1.41 |
testpath = '/'.join([dir, f])
|
682 |
|
|
pth = testpath.split('./src/python/')
|
683 |
|
|
pth.append(pth[1].replace('/', '_t/').replace('.', '_t.'))
|
684 |
metson |
1.42 |
if pkg['name'] == pth[2].rsplit('/', 1)[0].replace('_t/', '/'):
|
685 |
|
|
# pkg hasn't changed, increment counts
|
686 |
|
|
pkg['files'] += 1
|
687 |
|
|
else:
|
688 |
|
|
# new package, print stats for old package
|
689 |
|
|
pkgcnt += 1
|
690 |
|
|
if pkg['name'] != '' and pkg['files'] > 0:
|
691 |
|
|
print 'Package %s has coverage %.1f percent' % (pkg['name'],
|
692 |
|
|
(float(pkg['tests'])/float(pkg['files']) * 100))
|
693 |
|
|
# and start over for the new package
|
694 |
|
|
pkg['name'] = pth[2].rsplit('/', 1)[0].replace('_t/', '/')
|
695 |
|
|
# do global book keeping
|
696 |
|
|
files += pkg['files']
|
697 |
|
|
tests += pkg['tests']
|
698 |
|
|
pkg['files'] = 0
|
699 |
|
|
pkg['tests'] = 0
|
700 |
metson |
1.41 |
pth[1] = 'test/python'
|
701 |
|
|
testpath = '/'.join(pth)
|
702 |
metson |
1.42 |
try:
|
703 |
|
|
os.stat(testpath)
|
704 |
|
|
pkg['tests'] += 1
|
705 |
metson |
1.41 |
except:
|
706 |
metson |
1.42 |
pass
|
707 |
|
|
|
708 |
|
|
coverage = (float(tests) / float(files)) * 100
|
709 |
metson |
1.43 |
print '----------------------------------------------------------------'
|
710 |
metson |
1.42 |
print 'Code coverage (%s packages) is %.2f percent' % (pkgcnt, coverage)
|
711 |
metson |
1.41 |
return coverage
|
712 |
metson |
1.49 |
|
713 |
|
|
class EnvCommand(Command):
|
714 |
|
|
description = "Configure the PYTHONPATH, DATABASE and PATH variables to" +\
|
715 |
|
|
"some sensible defaults, if not already set. Call with -q when eval-ing," +\
|
716 |
metson |
1.50 |
""" e.g.:
|
717 |
metson |
1.49 |
eval `python setup.py -q env`
|
718 |
|
|
"""
|
719 |
|
|
|
720 |
|
|
user_options = [ ]
|
721 |
|
|
|
722 |
|
|
def initialize_options(self):
|
723 |
|
|
pass
|
724 |
|
|
|
725 |
|
|
def finalize_options(self):
|
726 |
|
|
pass
|
727 |
metson |
1.41 |
|
728 |
metson |
1.49 |
def run(self):
|
729 |
|
|
if not os.getenv('DATABASE', False):
|
730 |
|
|
# Use an in memory sqlite one if none is configured.
|
731 |
|
|
print 'export DATABASE=sqlite://'
|
732 |
|
|
|
733 |
metson |
1.93 |
here = get_relative_path()
|
734 |
|
|
|
735 |
metson |
1.49 |
tests = here + '/test/python'
|
736 |
|
|
source = here + '/src/python'
|
737 |
|
|
webpth = source + '/WMCore/WebTools'
|
738 |
metson |
1.93 |
|
739 |
|
|
pypath=os.getenv('PYTHONPATH', '').strip(':').split(':')
|
740 |
metson |
1.49 |
|
741 |
|
|
for pth in [tests, source]:
|
742 |
|
|
if pth not in pypath:
|
743 |
|
|
pypath.append(pth)
|
744 |
metson |
1.50 |
|
745 |
|
|
# We might want to add other executables to PATH
|
746 |
metson |
1.49 |
expath=os.getenv('PATH', '').split(':')
|
747 |
|
|
for pth in [webpth]:
|
748 |
|
|
if pth not in expath:
|
749 |
|
|
expath.append(pth)
|
750 |
|
|
|
751 |
|
|
print 'export PYTHONPATH=%s' % ':'.join(pypath)
|
752 |
|
|
print 'export PATH=%s' % ':'.join(expath)
|
753 |
metson |
1.85 |
|
754 |
|
|
#We want the WMCORE root set, too
|
755 |
meloam |
1.89 |
print 'export WMCOREBASE=%s' % get_relative_path()
|
756 |
metson |
1.85 |
|
757 |
metson |
1.49 |
|
758 |
metson |
1.22 |
def getPackages(package_dirs = []):
|
759 |
|
|
packages = []
|
760 |
|
|
for dir in package_dirs:
|
761 |
|
|
for dirpath, dirnames, filenames in os.walk('./%s' % dir):
|
762 |
|
|
# Exclude things here
|
763 |
|
|
if dirpath not in ['./src/python/', './src/python/IMProv']:
|
764 |
|
|
pathelements = dirpath.split('/')
|
765 |
|
|
if not 'CVS' in pathelements:
|
766 |
|
|
path = pathelements[3:]
|
767 |
|
|
packages.append('.'.join(path))
|
768 |
|
|
return packages
|
769 |
|
|
|
770 |
|
|
package_dir = {'WMCore': 'src/python/WMCore',
|
771 |
|
|
'WMComponent' : 'src/python/WMComponent',
|
772 |
|
|
'WMQuality' : 'src/python/WMQuality'}
|
773 |
|
|
|
774 |
|
|
setup (name = 'wmcore',
|
775 |
|
|
version = '1.0',
|
776 |
metson |
1.36 |
maintainer_email = 'hn-cms-wmDevelopment@cern.ch',
|
777 |
meloam |
1.88 |
cmdclass = {#'test': TestCommand,
|
778 |
metson |
1.23 |
'clean': CleanCommand,
|
779 |
metson |
1.36 |
'lint': LintCommand,
|
780 |
|
|
'report': ReportCommand,
|
781 |
metson |
1.41 |
'coverage': CoverageCommand ,
|
782 |
metson |
1.49 |
'missing': DumbCoverageCommand,
|
783 |
meloam |
1.88 |
'env': EnvCommand,
|
784 |
|
|
'test' : NoseCommand },
|
785 |
metson |
1.22 |
package_dir = package_dir,
|
786 |
|
|
packages = getPackages(package_dir.values()),)
|
787 |
fvlingen |
1.7 |
|