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