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 |
meloam |
1.111 |
import time
|
11 |
metson |
1.51 |
#PyLinter and coverage aren't standard, but aren't strictly necessary
|
12 |
meloam |
1.117 |
# you should get them though
|
13 |
metson |
1.51 |
can_lint = False
|
14 |
|
|
can_coverage = False
|
15 |
meloam |
1.88 |
can_nose = False
|
16 |
sfoulkes |
1.29 |
try:
|
17 |
metson |
1.87 |
from pylint.lint import Run
|
18 |
metson |
1.85 |
from pylint.lint import preprocess_options, cb_init_hook
|
19 |
|
|
from pylint import checkers
|
20 |
metson |
1.51 |
can_lint = True
|
21 |
|
|
except:
|
22 |
|
|
pass
|
23 |
|
|
try:
|
24 |
meloam |
1.47 |
import coverage
|
25 |
metson |
1.51 |
can_coverage = True
|
26 |
sfoulkes |
1.29 |
except:
|
27 |
|
|
pass
|
28 |
fvlingen |
1.1 |
|
29 |
meloam |
1.88 |
try:
|
30 |
|
|
import nose
|
31 |
meloam |
1.115 |
from nose.plugins import Plugin, PluginTester
|
32 |
|
|
import nose.failure
|
33 |
|
|
import nose.case
|
34 |
meloam |
1.88 |
can_nose = True
|
35 |
|
|
except:
|
36 |
|
|
pass
|
37 |
|
|
|
38 |
|
|
if can_nose:
|
39 |
meloam |
1.115 |
class DetailedOutputter(Plugin):
|
40 |
|
|
name = "detailed"
|
41 |
|
|
def __init__(self):
|
42 |
|
|
pass
|
43 |
|
|
|
44 |
|
|
def setOutputStream(self, stream):
|
45 |
|
|
"""Get handle on output stream so the plugin can print id #s
|
46 |
|
|
"""
|
47 |
|
|
self.stream = stream
|
48 |
|
|
|
49 |
|
|
def beforeTest(self, test):
|
50 |
|
|
if ( test.id().startswith('nose.failure') ):
|
51 |
|
|
pass
|
52 |
|
|
#Plugin.beforeTest(self, test)
|
53 |
|
|
else:
|
54 |
|
|
|
55 |
|
|
self.stream.write('%s -- ' % (test.id(), ))
|
56 |
|
|
|
57 |
|
|
def configure(self, options, conf):
|
58 |
|
|
"""
|
59 |
|
|
Configure plugin. Skip plugin is enabled by default.
|
60 |
|
|
"""
|
61 |
|
|
self.enabled = True
|
62 |
|
|
|
63 |
meloam |
1.96 |
class TestCommand(Command):
|
64 |
meloam |
1.100 |
"""Runs our test suite"""
|
65 |
meloam |
1.111 |
# WARNING WARNING WARNING
|
66 |
|
|
# if you set this to true, I will really delete your database
|
67 |
|
|
# after every test
|
68 |
|
|
# WARNING WARNING WARNING
|
69 |
|
|
user_options = [('reallyDeleteMyDatabaseAfterEveryTest=',
|
70 |
|
|
None,
|
71 |
meloam |
1.114 |
'If you set this I WILL DELETE YOUR DATABASE AFTER EVERY TEST. DO NOT RUN ON A PRODUCTION SYSTEM'),
|
72 |
|
|
('buildBotMode=',
|
73 |
|
|
None,
|
74 |
|
|
'Are we running inside buildbot?')]
|
75 |
meloam |
1.88 |
|
76 |
|
|
def initialize_options(self):
|
77 |
meloam |
1.111 |
self.reallyDeleteMyDatabaseAfterEveryTest = False
|
78 |
meloam |
1.114 |
self.buildBotMode = False
|
79 |
meloam |
1.88 |
pass
|
80 |
|
|
|
81 |
|
|
def finalize_options(self):
|
82 |
|
|
pass
|
83 |
|
|
|
84 |
|
|
def run(self):
|
85 |
meloam |
1.111 |
if self.reallyDeleteMyDatabaseAfterEveryTest:
|
86 |
|
|
print "#### WE ARE DELETING YOUR DATABASE. 3 SECONDS TO CANCEL ####"
|
87 |
|
|
sys.stdout.flush()
|
88 |
|
|
import WMQuality.TestInit
|
89 |
|
|
WMQuality.TestInit.deleteDatabaseAfterEveryTest( "I'm Serious" )
|
90 |
meloam |
1.115 |
time.sleep(4)
|
91 |
meloam |
1.114 |
|
92 |
|
|
if self.buildBotMode:
|
93 |
|
|
retval = nose.run(argv=[__file__,'--all-modules','-v','test/python'])
|
94 |
|
|
else:
|
95 |
meloam |
1.115 |
retval = nose.run(argv=['sup.py','--all-modules','-v','test/python','-a','!integration'],
|
96 |
|
|
addplugins=[DetailedOutputter()])
|
97 |
meloam |
1.111 |
|
98 |
meloam |
1.94 |
if retval:
|
99 |
meloam |
1.107 |
sys.exit( 0 )
|
100 |
meloam |
1.94 |
else:
|
101 |
meloam |
1.107 |
sys.exit( 1 )
|
102 |
meloam |
1.88 |
else:
|
103 |
meloam |
1.101 |
class TestCommand(Command):
|
104 |
meloam |
1.88 |
user_options = [ ]
|
105 |
|
|
def run(self):
|
106 |
meloam |
1.97 |
print "Nose isn't installed. You must install the nose package to run tests (easy_install nose might do it)"
|
107 |
meloam |
1.109 |
sys.exit(1)
|
108 |
meloam |
1.88 |
pass
|
109 |
|
|
|
110 |
|
|
def initialize_options(self):
|
111 |
|
|
pass
|
112 |
|
|
|
113 |
|
|
def finalize_options(self):
|
114 |
|
|
pass
|
115 |
|
|
pass
|
116 |
|
|
|
117 |
metson |
1.87 |
if can_lint:
|
118 |
|
|
class LinterRun(Run):
|
119 |
|
|
def __init__(self, args, reporter=None):
|
120 |
|
|
self._rcfile = None
|
121 |
|
|
self._plugins = []
|
122 |
|
|
preprocess_options(args, {
|
123 |
|
|
# option: (callback, takearg)
|
124 |
|
|
'rcfile': (self.cb_set_rcfile, True),
|
125 |
|
|
'load-plugins': (self.cb_add_plugins, True),
|
126 |
|
|
})
|
127 |
|
|
self.linter = linter = self.LinterClass((
|
128 |
|
|
('rcfile',
|
129 |
|
|
{'action' : 'callback', 'callback' : lambda *args: 1,
|
130 |
|
|
'type': 'string', 'metavar': '<file>',
|
131 |
|
|
'help' : 'Specify a configuration file.'}),
|
132 |
|
|
|
133 |
|
|
('init-hook',
|
134 |
|
|
{'action' : 'callback', 'type' : 'string', 'metavar': '<code>',
|
135 |
|
|
'callback' : cb_init_hook,
|
136 |
|
|
'help' : 'Python code to execute, usually for sys.path \
|
137 |
|
|
manipulation such as pygtk.require().'}),
|
138 |
|
|
|
139 |
|
|
('help-msg',
|
140 |
|
|
{'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
|
141 |
|
|
'callback' : self.cb_help_message,
|
142 |
|
|
'group': 'Commands',
|
143 |
|
|
'help' : '''Display a help message for the given message id and \
|
144 |
|
|
exit. The value may be a comma separated list of message ids.'''}),
|
145 |
|
|
|
146 |
|
|
('list-msgs',
|
147 |
|
|
{'action' : 'callback', 'metavar': '<msg-id>',
|
148 |
|
|
'callback' : self.cb_list_messages,
|
149 |
|
|
'group': 'Commands',
|
150 |
|
|
'help' : "Generate pylint's full documentation."}),
|
151 |
|
|
|
152 |
|
|
('generate-rcfile',
|
153 |
|
|
{'action' : 'callback', 'callback' : self.cb_generate_config,
|
154 |
|
|
'group': 'Commands',
|
155 |
|
|
'help' : '''Generate a sample configuration file according to \
|
156 |
|
|
the current configuration. You can put other options before this one to get \
|
157 |
|
|
them in the generated configuration.'''}),
|
158 |
|
|
|
159 |
|
|
('generate-man',
|
160 |
|
|
{'action' : 'callback', 'callback' : self.cb_generate_manpage,
|
161 |
|
|
'group': 'Commands',
|
162 |
|
|
'help' : "Generate pylint's man page.",'hide': 'True'}),
|
163 |
|
|
|
164 |
|
|
('errors-only',
|
165 |
|
|
{'action' : 'callback', 'callback' : self.cb_error_mode,
|
166 |
|
|
'short': 'e',
|
167 |
|
|
'help' : '''In error mode, checkers without error messages are \
|
168 |
|
|
disabled and for others, only the ERROR messages are displayed, and no reports \
|
169 |
|
|
are done by default'''}),
|
170 |
|
|
|
171 |
|
|
('profile',
|
172 |
|
|
{'type' : 'yn', 'metavar' : '<y_or_n>',
|
173 |
|
|
'default': False,
|
174 |
|
|
'help' : 'Profiled execution.'}),
|
175 |
|
|
|
176 |
|
|
), option_groups=self.option_groups,
|
177 |
|
|
reporter=reporter, pylintrc=self._rcfile)
|
178 |
|
|
# register standard checkers
|
179 |
|
|
checkers.initialize(linter)
|
180 |
|
|
# load command line plugins
|
181 |
|
|
linter.load_plugin_modules(self._plugins)
|
182 |
|
|
# read configuration
|
183 |
|
|
linter.disable_message('W0704')
|
184 |
|
|
linter.read_config_file()
|
185 |
|
|
# is there some additional plugins in the file configuration, in
|
186 |
|
|
config_parser = linter._config_parser
|
187 |
|
|
if config_parser.has_option('MASTER', 'load-plugins'):
|
188 |
|
|
plugins = splitstrip(config_parser.get('MASTER', 'load-plugins'))
|
189 |
|
|
linter.load_plugin_modules(plugins)
|
190 |
|
|
# now we can load file config and command line, plugins (which can
|
191 |
|
|
# provide options) have been registered
|
192 |
|
|
linter.load_config_file()
|
193 |
|
|
if reporter:
|
194 |
|
|
# if a custom reporter is provided as argument, it may be overriden
|
195 |
|
|
# by file parameters, so re-set it here, but before command line
|
196 |
|
|
# parsing so it's still overrideable by command line option
|
197 |
|
|
linter.set_reporter(reporter)
|
198 |
|
|
args = linter.load_command_line_configuration(args)
|
199 |
|
|
# insert current working directory to the python path to have a correct
|
200 |
|
|
# behaviour
|
201 |
|
|
sys.path.insert(0, os.getcwd())
|
202 |
|
|
if self.linter.config.profile:
|
203 |
|
|
print >> sys.stderr, '** profiled run'
|
204 |
|
|
from hotshot import Profile, stats
|
205 |
|
|
prof = Profile('stones.prof')
|
206 |
|
|
prof.runcall(linter.check, args)
|
207 |
|
|
prof.close()
|
208 |
|
|
data = stats.load('stones.prof')
|
209 |
|
|
data.strip_dirs()
|
210 |
|
|
data.sort_stats('time', 'calls')
|
211 |
|
|
data.print_stats(30)
|
212 |
|
|
sys.path.pop(0)
|
213 |
|
|
|
214 |
|
|
def cb_set_rcfile(self, name, value):
|
215 |
|
|
"""callback for option preprocessing (ie before optik parsing)"""
|
216 |
|
|
self._rcfile = value
|
217 |
|
|
|
218 |
|
|
def cb_add_plugins(self, name, value):
|
219 |
|
|
"""callback for option preprocessing (ie before optik parsing)"""
|
220 |
|
|
self._plugins.extend(splitstrip(value))
|
221 |
|
|
|
222 |
|
|
def cb_error_mode(self, *args, **kwargs):
|
223 |
|
|
"""error mode:
|
224 |
|
|
* checkers without error messages are disabled
|
225 |
|
|
* for others, only the ERROR messages are displayed
|
226 |
|
|
* disable reports
|
227 |
|
|
* do not save execution information
|
228 |
|
|
"""
|
229 |
|
|
self.linter.disable_noerror_checkers()
|
230 |
|
|
self.linter.set_option('disable-msg-cat', 'WCRFI')
|
231 |
|
|
self.linter.set_option('reports', False)
|
232 |
|
|
self.linter.set_option('persistent', False)
|
233 |
|
|
|
234 |
|
|
def cb_generate_config(self, *args, **kwargs):
|
235 |
|
|
"""optik callback for sample config file generation"""
|
236 |
|
|
self.linter.generate_config(skipsections=('COMMANDS',))
|
237 |
|
|
|
238 |
|
|
def cb_generate_manpage(self, *args, **kwargs):
|
239 |
|
|
"""optik callback for sample config file generation"""
|
240 |
|
|
from pylint import __pkginfo__
|
241 |
|
|
self.linter.generate_manpage(__pkginfo__)
|
242 |
|
|
|
243 |
|
|
def cb_help_message(self, option, opt_name, value, parser):
|
244 |
|
|
"""optik callback for printing some help about a particular message"""
|
245 |
|
|
self.linter.help_message(splitstrip(value))
|
246 |
|
|
|
247 |
|
|
def cb_list_messages(self, option, opt_name, value, parser):
|
248 |
|
|
"""optik callback for printing available messages"""
|
249 |
|
|
self.linter.list_messages()
|
250 |
|
|
else:
|
251 |
|
|
class LinterRun:
|
252 |
|
|
def __init__(self):
|
253 |
|
|
pass
|
254 |
metson |
1.85 |
|
255 |
metson |
1.22 |
"""
|
256 |
|
|
Build, clean and test the WMCore package.
|
257 |
|
|
"""
|
258 |
metson |
1.112 |
|
259 |
|
|
def get_relative_path():
|
260 |
|
|
return os.path.dirname(os.path.abspath(os.path.join(os.getcwd(), sys.argv[0])))
|
261 |
metson |
1.22 |
|
262 |
metson |
1.92 |
def generate_filelist(basepath=None, recurse=True, ignore=False):
|
263 |
metson |
1.85 |
if basepath:
|
264 |
|
|
walkpath = os.path.join(get_relative_path(), 'src/python', basepath)
|
265 |
|
|
else:
|
266 |
|
|
walkpath = os.path.join(get_relative_path(), 'src/python')
|
267 |
|
|
|
268 |
metson |
1.36 |
files = []
|
269 |
metson |
1.85 |
|
270 |
|
|
if walkpath.endswith('.py'):
|
271 |
metson |
1.92 |
if ignore and walkpath.endswith(ignore):
|
272 |
|
|
files.append(walkpath)
|
273 |
metson |
1.85 |
else:
|
274 |
|
|
for dirpath, dirnames, filenames in os.walk(walkpath):
|
275 |
|
|
# skipping CVS directories and their contents
|
276 |
|
|
pathelements = dirpath.split('/')
|
277 |
|
|
result = []
|
278 |
|
|
if not 'CVS' in pathelements:
|
279 |
|
|
# to build up a list of file names which contain tests
|
280 |
|
|
for file in filenames:
|
281 |
|
|
if file.endswith('.py'):
|
282 |
|
|
filepath = '/'.join([dirpath, file])
|
283 |
|
|
files.append(filepath)
|
284 |
|
|
|
285 |
|
|
if len(files) == 0 and recurse:
|
286 |
|
|
files = generate_filelist(basepath + '.py', not recurse)
|
287 |
|
|
|
288 |
metson |
1.36 |
return files
|
289 |
|
|
|
290 |
metson |
1.85 |
def lint_score(stats, evaluation):
|
291 |
|
|
return eval(evaluation, {}, stats)
|
292 |
|
|
|
293 |
|
|
def lint_files(files, reports=False):
|
294 |
metson |
1.36 |
"""
|
295 |
metson |
1.85 |
lint a (list of) file(s) and return the results as a dictionary containing
|
296 |
|
|
filename : result_dict
|
297 |
metson |
1.36 |
"""
|
298 |
metson |
1.85 |
|
299 |
|
|
rcfile=os.path.join(get_relative_path(),'standards/.pylintrc')
|
300 |
|
|
|
301 |
metson |
1.92 |
arguements = ['--rcfile=%s' % rcfile, '--ignore=DefaultConfig.py']
|
302 |
metson |
1.86 |
|
303 |
|
|
if not reports:
|
304 |
|
|
arguements.append('-rn')
|
305 |
|
|
|
306 |
metson |
1.85 |
arguements.extend(files)
|
307 |
|
|
|
308 |
|
|
lntr = LinterRun(arguements)
|
309 |
|
|
|
310 |
|
|
results = {}
|
311 |
|
|
for file in files:
|
312 |
|
|
lntr.linter.check(file)
|
313 |
|
|
results[file] = {'stats': lntr.linter.stats,
|
314 |
|
|
'score': lint_score(lntr.linter.stats,
|
315 |
|
|
lntr.linter.config.evaluation)
|
316 |
|
|
}
|
317 |
|
|
if reports:
|
318 |
|
|
print '----------------------------------'
|
319 |
|
|
print 'Your code has been rated at %.2f/10' % \
|
320 |
|
|
lint_score(lntr.linter.stats, lntr.linter.config.evaluation)
|
321 |
metson |
1.36 |
|
322 |
metson |
1.85 |
return results, lntr.linter.config.evaluation
|
323 |
meloam |
1.69 |
|
324 |
metson |
1.21 |
class CleanCommand(Command):
|
325 |
metson |
1.49 |
description = "Clean up (delete) compiled files"
|
326 |
metson |
1.21 |
user_options = [ ]
|
327 |
|
|
|
328 |
|
|
def initialize_options(self):
|
329 |
|
|
self._clean_me = [ ]
|
330 |
|
|
for root, dirs, files in os.walk('.'):
|
331 |
|
|
for f in files:
|
332 |
|
|
if f.endswith('.pyc'):
|
333 |
|
|
self._clean_me.append(pjoin(root, f))
|
334 |
|
|
|
335 |
|
|
def finalize_options(self):
|
336 |
|
|
pass
|
337 |
|
|
|
338 |
|
|
def run(self):
|
339 |
|
|
for clean_me in self._clean_me:
|
340 |
|
|
try:
|
341 |
|
|
os.unlink(clean_me)
|
342 |
|
|
except:
|
343 |
|
|
pass
|
344 |
|
|
|
345 |
metson |
1.23 |
class LintCommand(Command):
|
346 |
metson |
1.49 |
description = "Lint all files in the src tree"
|
347 |
metson |
1.24 |
"""
|
348 |
|
|
TODO: better format the test results, get some global result, make output
|
349 |
|
|
more buildbot friendly.
|
350 |
|
|
"""
|
351 |
|
|
|
352 |
metson |
1.86 |
user_options = [ ('package=', 'p', 'package to lint, default to None'),
|
353 |
|
|
('report', 'r', 'return a detailed lint report, default False')]
|
354 |
metson |
1.23 |
|
355 |
|
|
def initialize_options(self):
|
356 |
metson |
1.85 |
self._dir = get_relative_path()
|
357 |
|
|
self.package = None
|
358 |
metson |
1.86 |
self.report = False
|
359 |
metson |
1.85 |
|
360 |
metson |
1.23 |
def finalize_options(self):
|
361 |
metson |
1.86 |
if self.report:
|
362 |
|
|
self.report = True
|
363 |
|
|
|
364 |
metson |
1.23 |
def run(self):
|
365 |
|
|
'''
|
366 |
|
|
Find the code and run lint on it
|
367 |
|
|
'''
|
368 |
metson |
1.51 |
if can_lint:
|
369 |
metson |
1.85 |
srcpypath = os.path.join(self._dir, 'src/python/')
|
370 |
|
|
|
371 |
metson |
1.51 |
sys.path.append(srcpypath)
|
372 |
|
|
|
373 |
metson |
1.85 |
files_to_lint = []
|
374 |
|
|
|
375 |
|
|
if self.package:
|
376 |
|
|
if self.package.endswith('.py'):
|
377 |
|
|
cnt = self.package.count('.') - 1
|
378 |
metson |
1.92 |
files_to_lint = generate_filelist(self.package.replace('.', '/', cnt), 'DeafultConfig.py')
|
379 |
metson |
1.85 |
else:
|
380 |
metson |
1.92 |
files_to_lint = generate_filelist(self.package.replace('.', '/'), 'DeafultConfig.py')
|
381 |
metson |
1.85 |
else:
|
382 |
metson |
1.92 |
files_to_lint = generate_filelist(ignore='DeafultConfig.py')
|
383 |
metson |
1.86 |
|
384 |
|
|
results, evaluation = lint_files(files_to_lint, self.report)
|
385 |
metson |
1.85 |
ln = len(results)
|
386 |
|
|
scr = 0
|
387 |
|
|
print
|
388 |
|
|
for k, v in results.items():
|
389 |
|
|
print "%s: %.2f/10" % (k.replace('src/python/', ''), v['score'])
|
390 |
|
|
scr += v['score']
|
391 |
|
|
if ln > 1:
|
392 |
|
|
print '--------------------------------------------------------'
|
393 |
|
|
print 'Average pylint score for %s is: %.2f/10' % (self.package,
|
394 |
|
|
scr/ln)
|
395 |
|
|
|
396 |
metson |
1.51 |
else:
|
397 |
|
|
print 'You need to install pylint before using the lint command'
|
398 |
metson |
1.36 |
|
399 |
|
|
class ReportCommand(Command):
|
400 |
metson |
1.49 |
description = "Generate a simple html report for ease of viewing in buildbot"
|
401 |
metson |
1.36 |
"""
|
402 |
|
|
To contain:
|
403 |
|
|
average lint score
|
404 |
|
|
% code coverage
|
405 |
|
|
list of classes missing tests
|
406 |
|
|
etc.
|
407 |
|
|
"""
|
408 |
|
|
|
409 |
|
|
user_options = [ ]
|
410 |
|
|
|
411 |
|
|
def initialize_options(self):
|
412 |
|
|
pass
|
413 |
|
|
|
414 |
|
|
def finalize_options(self):
|
415 |
|
|
pass
|
416 |
|
|
|
417 |
|
|
def run(self):
|
418 |
|
|
"""
|
419 |
|
|
run all the tests needed to generate the report and make an
|
420 |
|
|
html table
|
421 |
|
|
"""
|
422 |
|
|
files = generate_filelist()
|
423 |
|
|
|
424 |
|
|
error = 0
|
425 |
|
|
warning = 0
|
426 |
|
|
refactor = 0
|
427 |
|
|
convention = 0
|
428 |
|
|
statement = 0
|
429 |
|
|
|
430 |
metson |
1.85 |
srcpypath = '/'.join([get_relative_path(), 'src/python/'])
|
431 |
metson |
1.36 |
sys.path.append(srcpypath)
|
432 |
metson |
1.37 |
|
433 |
|
|
cfg = ConfigParser()
|
434 |
|
|
cfg.read('standards/.pylintrc')
|
435 |
|
|
|
436 |
metson |
1.40 |
# Supress stdout/stderr
|
437 |
metson |
1.38 |
sys.stderr = open('/dev/null', 'w')
|
438 |
metson |
1.39 |
sys.stdout = open('/dev/null', 'w')
|
439 |
meloam |
1.47 |
# wrap it in an exception handler, otherwise we can't see why it fails
|
440 |
|
|
try:
|
441 |
|
|
# lint the code
|
442 |
|
|
for stats in lint_files(files):
|
443 |
|
|
error += stats['error']
|
444 |
|
|
warning += stats['warning']
|
445 |
|
|
refactor += stats['refactor']
|
446 |
|
|
convention += stats['convention']
|
447 |
|
|
statement += stats['statement']
|
448 |
|
|
except Exception,e:
|
449 |
|
|
# and restore the stdout/stderr
|
450 |
|
|
sys.stderr = sys.__stderr__
|
451 |
|
|
sys.stdout = sys.__stderr__
|
452 |
|
|
raise e
|
453 |
metson |
1.37 |
|
454 |
metson |
1.40 |
# and restore the stdout/stderr
|
455 |
meloam |
1.47 |
sys.stderr = sys.__stderr__
|
456 |
|
|
sys.stdout = sys.__stderr__
|
457 |
metson |
1.38 |
|
458 |
metson |
1.37 |
stats = {'error': error,
|
459 |
|
|
'warning': warning,
|
460 |
|
|
'refactor': refactor,
|
461 |
|
|
'convention': convention,
|
462 |
|
|
'statement': statement}
|
463 |
|
|
|
464 |
|
|
lint_score = eval(cfg.get('MASTER', 'evaluation'), {}, stats)
|
465 |
metson |
1.36 |
coverage = 0 # TODO: calculate this
|
466 |
|
|
testless_classes = [] # TODO: generate this
|
467 |
|
|
|
468 |
|
|
print "<table>"
|
469 |
|
|
print "<tr>"
|
470 |
|
|
print "<td colspan=2><h1>WMCore test report</h1></td>"
|
471 |
|
|
print "</tr>"
|
472 |
|
|
print "<tr>"
|
473 |
|
|
print "<td>Average lint score</td>"
|
474 |
|
|
print "<td>%.2f</td>" % lint_score
|
475 |
|
|
print "</tr>"
|
476 |
|
|
print "<tr>"
|
477 |
|
|
print "<td>% code coverage</td>"
|
478 |
|
|
print "<td>%s</td>" % coverage
|
479 |
|
|
print "</tr>"
|
480 |
|
|
print "<tr>"
|
481 |
|
|
print "<td>Classes missing tests</td>"
|
482 |
|
|
print "<td>"
|
483 |
|
|
if len(testless_classes) == 0:
|
484 |
|
|
print "None"
|
485 |
|
|
else:
|
486 |
|
|
print "<ul>"
|
487 |
|
|
for c in testless_classes:
|
488 |
|
|
print "<li>%c</li>" % c
|
489 |
|
|
print "</ul>"
|
490 |
|
|
print "</td>"
|
491 |
|
|
print "</tr>"
|
492 |
|
|
print "</table>"
|
493 |
|
|
|
494 |
|
|
class CoverageCommand(Command):
|
495 |
metson |
1.49 |
description = "Run code coverage tests"
|
496 |
metson |
1.36 |
"""
|
497 |
metson |
1.49 |
To do this, we need to run all the unittests within the coverage
|
498 |
|
|
framework to record all the lines(and branches) executed
|
499 |
meloam |
1.47 |
unfortunately, we have multiple code paths per database schema, so
|
500 |
|
|
we need to find a way to merge them.
|
501 |
|
|
|
502 |
|
|
TODO: modify the test command to have a flag to record code coverage
|
503 |
|
|
the file thats used can then be used here, saving us from running
|
504 |
|
|
our tests twice
|
505 |
metson |
1.36 |
"""
|
506 |
|
|
|
507 |
|
|
user_options = [ ]
|
508 |
|
|
|
509 |
|
|
def initialize_options(self):
|
510 |
|
|
pass
|
511 |
|
|
|
512 |
|
|
def finalize_options(self):
|
513 |
|
|
pass
|
514 |
|
|
|
515 |
|
|
def run(self):
|
516 |
|
|
"""
|
517 |
|
|
Determine the code's test coverage and return that as a float
|
518 |
|
|
|
519 |
|
|
http://nedbatchelder.com/code/coverage/
|
520 |
|
|
"""
|
521 |
metson |
1.51 |
if can_coverage:
|
522 |
|
|
files = generate_filelist()
|
523 |
|
|
dataFile = None
|
524 |
|
|
cov = None
|
525 |
meloam |
1.48 |
|
526 |
metson |
1.51 |
# attempt to load previously cached coverage information if it exists
|
527 |
|
|
try:
|
528 |
|
|
dataFile = open("wmcore-coverage.dat","r")
|
529 |
|
|
cov = coverage.coverage(branch = True, data_file='wmcore-coverage.dat')
|
530 |
|
|
cov.load()
|
531 |
|
|
except:
|
532 |
|
|
cov = coverage.coverage(branch = True, )
|
533 |
|
|
cov.start()
|
534 |
|
|
runUnitTests()
|
535 |
|
|
cov.stop()
|
536 |
|
|
cov.save()
|
537 |
|
|
|
538 |
|
|
# we have our coverage information, now let's do something with it
|
539 |
|
|
# get a list of modules
|
540 |
|
|
cov.report(morfs = files, file=sys.stdout)
|
541 |
|
|
return 0
|
542 |
|
|
else:
|
543 |
|
|
print 'You need the coverage module installed before running the' +\
|
544 |
|
|
' coverage command'
|
545 |
metson |
1.36 |
|
546 |
metson |
1.41 |
class DumbCoverageCommand(Command):
|
547 |
metson |
1.49 |
description = "Run a simple coverage test - find classes that don't have a unit test"
|
548 |
metson |
1.41 |
|
549 |
|
|
user_options = [ ]
|
550 |
|
|
|
551 |
|
|
def initialize_options(self):
|
552 |
|
|
pass
|
553 |
|
|
|
554 |
|
|
def finalize_options(self):
|
555 |
|
|
pass
|
556 |
|
|
|
557 |
|
|
def run(self):
|
558 |
|
|
"""
|
559 |
|
|
Determine the code's test coverage in a dumb way and return that as a
|
560 |
|
|
float.
|
561 |
|
|
"""
|
562 |
metson |
1.42 |
print "This determines test coverage in a very crude manner. If your"
|
563 |
|
|
print "test file is incorrectly named it will not be counted, and"
|
564 |
|
|
print "result in a lower coverage score."
|
565 |
metson |
1.43 |
print '----------------------------------------------------------------'
|
566 |
metson |
1.42 |
filelist = generate_filelist()
|
567 |
|
|
tests = 0
|
568 |
|
|
files = 0
|
569 |
|
|
pkgcnt = 0
|
570 |
metson |
1.85 |
dir = get_relative_path()
|
571 |
metson |
1.42 |
pkg = {'name': '', 'files': 0, 'tests': 0}
|
572 |
|
|
for f in filelist:
|
573 |
metson |
1.41 |
testpath = '/'.join([dir, f])
|
574 |
|
|
pth = testpath.split('./src/python/')
|
575 |
|
|
pth.append(pth[1].replace('/', '_t/').replace('.', '_t.'))
|
576 |
metson |
1.42 |
if pkg['name'] == pth[2].rsplit('/', 1)[0].replace('_t/', '/'):
|
577 |
|
|
# pkg hasn't changed, increment counts
|
578 |
|
|
pkg['files'] += 1
|
579 |
|
|
else:
|
580 |
|
|
# new package, print stats for old package
|
581 |
|
|
pkgcnt += 1
|
582 |
|
|
if pkg['name'] != '' and pkg['files'] > 0:
|
583 |
|
|
print 'Package %s has coverage %.1f percent' % (pkg['name'],
|
584 |
|
|
(float(pkg['tests'])/float(pkg['files']) * 100))
|
585 |
|
|
# and start over for the new package
|
586 |
|
|
pkg['name'] = pth[2].rsplit('/', 1)[0].replace('_t/', '/')
|
587 |
|
|
# do global book keeping
|
588 |
|
|
files += pkg['files']
|
589 |
|
|
tests += pkg['tests']
|
590 |
|
|
pkg['files'] = 0
|
591 |
|
|
pkg['tests'] = 0
|
592 |
metson |
1.41 |
pth[1] = 'test/python'
|
593 |
|
|
testpath = '/'.join(pth)
|
594 |
metson |
1.42 |
try:
|
595 |
|
|
os.stat(testpath)
|
596 |
|
|
pkg['tests'] += 1
|
597 |
metson |
1.41 |
except:
|
598 |
metson |
1.42 |
pass
|
599 |
|
|
|
600 |
|
|
coverage = (float(tests) / float(files)) * 100
|
601 |
metson |
1.43 |
print '----------------------------------------------------------------'
|
602 |
metson |
1.42 |
print 'Code coverage (%s packages) is %.2f percent' % (pkgcnt, coverage)
|
603 |
metson |
1.41 |
return coverage
|
604 |
metson |
1.49 |
|
605 |
|
|
class EnvCommand(Command):
|
606 |
|
|
description = "Configure the PYTHONPATH, DATABASE and PATH variables to" +\
|
607 |
|
|
"some sensible defaults, if not already set. Call with -q when eval-ing," +\
|
608 |
metson |
1.50 |
""" e.g.:
|
609 |
metson |
1.49 |
eval `python setup.py -q env`
|
610 |
|
|
"""
|
611 |
|
|
|
612 |
|
|
user_options = [ ]
|
613 |
|
|
|
614 |
|
|
def initialize_options(self):
|
615 |
|
|
pass
|
616 |
|
|
|
617 |
|
|
def finalize_options(self):
|
618 |
|
|
pass
|
619 |
metson |
1.41 |
|
620 |
metson |
1.49 |
def run(self):
|
621 |
|
|
if not os.getenv('DATABASE', False):
|
622 |
|
|
# Use an in memory sqlite one if none is configured.
|
623 |
|
|
print 'export DATABASE=sqlite://'
|
624 |
metson |
1.110 |
if not os.getenv('COUCHURL', False):
|
625 |
|
|
# Use the default localhost URL if none is configured.
|
626 |
|
|
print 'export COUCHURL=localhost:5984'
|
627 |
metson |
1.93 |
here = get_relative_path()
|
628 |
|
|
|
629 |
metson |
1.49 |
tests = here + '/test/python'
|
630 |
|
|
source = here + '/src/python'
|
631 |
metson |
1.116 |
# Stuff we want on the path
|
632 |
|
|
exepth = [source + '/WMCore/WebTools',
|
633 |
|
|
here + '/bin']
|
634 |
metson |
1.93 |
|
635 |
|
|
pypath=os.getenv('PYTHONPATH', '').strip(':').split(':')
|
636 |
metson |
1.49 |
|
637 |
|
|
for pth in [tests, source]:
|
638 |
|
|
if pth not in pypath:
|
639 |
|
|
pypath.append(pth)
|
640 |
metson |
1.50 |
|
641 |
|
|
# We might want to add other executables to PATH
|
642 |
metson |
1.49 |
expath=os.getenv('PATH', '').split(':')
|
643 |
metson |
1.116 |
for pth in exepth:
|
644 |
metson |
1.49 |
if pth not in expath:
|
645 |
|
|
expath.append(pth)
|
646 |
|
|
|
647 |
|
|
print 'export PYTHONPATH=%s' % ':'.join(pypath)
|
648 |
|
|
print 'export PATH=%s' % ':'.join(expath)
|
649 |
metson |
1.85 |
|
650 |
|
|
#We want the WMCORE root set, too
|
651 |
metson |
1.113 |
print 'export WMCORE_ROOT=%s' % get_relative_path()
|
652 |
|
|
print 'export WMCOREBASE=$WMCORE_ROOT'
|
653 |
|
|
print 'export WTBASE=$WMCORE_ROOT/src'
|
654 |
|
|
|
655 |
metson |
1.85 |
|
656 |
metson |
1.49 |
|
657 |
metson |
1.22 |
def getPackages(package_dirs = []):
|
658 |
|
|
packages = []
|
659 |
|
|
for dir in package_dirs:
|
660 |
|
|
for dirpath, dirnames, filenames in os.walk('./%s' % dir):
|
661 |
|
|
# Exclude things here
|
662 |
|
|
if dirpath not in ['./src/python/', './src/python/IMProv']:
|
663 |
|
|
pathelements = dirpath.split('/')
|
664 |
|
|
if not 'CVS' in pathelements:
|
665 |
|
|
path = pathelements[3:]
|
666 |
|
|
packages.append('.'.join(path))
|
667 |
|
|
return packages
|
668 |
|
|
|
669 |
|
|
package_dir = {'WMCore': 'src/python/WMCore',
|
670 |
|
|
'WMComponent' : 'src/python/WMComponent',
|
671 |
|
|
'WMQuality' : 'src/python/WMQuality'}
|
672 |
|
|
|
673 |
|
|
setup (name = 'wmcore',
|
674 |
|
|
version = '1.0',
|
675 |
metson |
1.36 |
maintainer_email = 'hn-cms-wmDevelopment@cern.ch',
|
676 |
meloam |
1.96 |
cmdclass = {'clean': CleanCommand,
|
677 |
metson |
1.36 |
'lint': LintCommand,
|
678 |
|
|
'report': ReportCommand,
|
679 |
metson |
1.41 |
'coverage': CoverageCommand ,
|
680 |
metson |
1.49 |
'missing': DumbCoverageCommand,
|
681 |
meloam |
1.88 |
'env': EnvCommand,
|
682 |
meloam |
1.96 |
'test' : TestCommand },
|
683 |
metson |
1.22 |
package_dir = package_dir,
|
684 |
|
|
packages = getPackages(package_dir.values()),)
|