ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WMCORE/setup.py
Revision: 1.80
Committed: Thu Jan 21 00:07:22 2010 UTC (15 years, 3 months ago) by meloam
Content type: text/x-python
Branch: MAIN
Changes since 1.79: +5 -50 lines
Log Message:
removing some debug statements

File Contents

# User Rev Content
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.37 from ConfigParser import ConfigParser
7 meloam 1.53 import os, sys, os.path
8 metson 1.51 #PyLinter and coverage aren't standard, but aren't strictly necessary
9     can_lint = False
10     can_coverage = False
11 sfoulkes 1.29 try:
12     from pylint import lint
13 metson 1.51 can_lint = True
14     except:
15     pass
16     try:
17 meloam 1.47 import coverage
18 metson 1.51 can_coverage = True
19 sfoulkes 1.29 except:
20     pass
21 fvlingen 1.1
22 metson 1.22 """
23     Build, clean and test the WMCore package.
24     """
25    
26 metson 1.36 def generate_filelist():
27     files = []
28 meloam 1.48 for dirpath, dirnames, filenames in os.walk('src/python/'):
29 metson 1.36 # skipping CVS directories and their contents
30     pathelements = dirpath.split('/')
31     result = []
32     if not 'CVS' in pathelements:
33     # to build up a list of file names which contain tests
34     for file in filenames:
35     if file.endswith('.py'):
36     filepath = '/'.join([dirpath, file])
37     files.append(filepath)
38     return files
39    
40     def lint_files(files):
41     """
42     lint a (list of) file(s) and return the results
43     """
44     input = ['--rcfile=standards/.pylintrc',
45     '--output-format=parseable',
46     '--reports=n', ]
47     input.extend(files)
48 meloam 1.47 lint_result = lint.Run(input)
49 metson 1.36 return lint_result.linter.stats
50    
51 meloam 1.69
52    
53     import logging
54 meloam 1.71 import os, os.path
55 meloam 1.69 import unittest
56    
57 meloam 1.70 MODULE_EXTENSIONS = set('.py'.split())
58     ## bad bad bad global variable, FIXME
59     all_test_suites = []
60 meloam 1.69
61 meloam 1.72
62 meloam 1.69 def get_test_suites(path):
63     """:return: Iterable of suites for the packages/modules
64     present under :param:`path`.
65     """
66 meloam 1.70 logging.info('Base path: %s', path)
67 meloam 1.69 suites = []
68 meloam 1.70 os.path.walk(path, unit_test_extractor, (path, suites))
69     logging.info('Got suites: %s', all_test_suites)
70     return all_test_suites
71 meloam 1.69
72 meloam 1.76 import re
73     def listFiles(dir):
74     fileList = []
75     basedir = dir
76     for item in os.listdir(dir):
77     if os.path.isfile(os.path.join(basedir,item)):
78     fileList.append(os.path.join(basedir,item))
79     elif os.path.isdir(os.path.join(basedir,item)):
80     fileList.extend(listFiles(os.path.join(basedir,item)))
81    
82     return fileList
83    
84    
85     import pprint
86 meloam 1.69
87 meloam 1.47 def runUnitTests():
88     # runs all the unittests, returning the testresults object
89     testfiles = []
90     # Add the test and src directory to the python path
91     mydir = os.getcwd()
92     # todo: not portable
93 meloam 1.57 testspypath = '/'.join([mydir, 'test/python/'])
94     srcpypath = '/'.join([mydir, 'src/python/'])
95 meloam 1.47 sys.path.append(testspypath)
96     sys.path.append(srcpypath)
97 meloam 1.70 logging.basicConfig(level=logging.DEBUG)
98 meloam 1.76 path = os.path.abspath(os.path.dirname(sys.argv[0]))
99     files = listFiles(path)
100     test = re.compile("_t\.py$", re.IGNORECASE)
101     files = filter(test.search, files)
102     filenameToModuleName = lambda f: os.path.splitext(f)[0]
103     moduleNames = map(filenameToModuleName, files)
104     stripBeginning = lambda f: f[ len(path) + len('/test/python/'): ]
105     moduleNames2 = map( stripBeginning, moduleNames )
106     replaceSlashes = lambda f: f.replace('/','.')
107     moduleNames3 = map( replaceSlashes, moduleNames2 )
108 meloam 1.80 modules = []
109     loadFail = []
110 meloam 1.76 for oneModule in moduleNames3:
111     try:
112 meloam 1.78 __import__(oneModule)
113     modules.append(sys.modules[oneModule])
114 meloam 1.79 except Exception, e:
115 meloam 1.80 print "ERROR: Can't load %s - %s" % (oneModule, e)
116     loadFail.append(oneModule)
117    
118 meloam 1.76 load = unittest.defaultTestLoader.loadTestsFromModule
119     globalSuite = unittest.TestSuite(map(load, modules))
120     # # logging.basicConfig(level=logging.WARN)
121     # package_path = os.path.dirname(mydir + '/test/python/')
122     # print "path: %s " % package_path
123     # suites = get_test_suites(package_path)
124     # testCaseCount = 0
125     # totallySuite = unittest.TestSuite()
126     # totallySuite.addTests(suites)
127     # print suites
128     result = unittest.TextTestRunner(verbosity=2).run(globalSuite)
129 meloam 1.70
130 meloam 1.59 #sys.stdout = sys.__stdout__
131     #sys.stderr = sys.__stderr__
132 meloam 1.57
133     print sys.path
134 meloam 1.76 return (result, [], globalSuite.countTestCases())
135 meloam 1.47
136    
137 metson 1.21 class TestCommand(Command):
138 metson 1.49 description = "Handle setup.py test with this class - walk through the " + \
139     "directory structure building up a list of tests, then build a test " + \
140     " suite and execute it."
141 metson 1.22 """
142     TODO: Pull database URL's from environment, and skip tests where database
143     URL is not present (e.g. for a slave without Oracle connection)
144 meloam 1.46
145     TODO: need to build a separate test runner for each test file, python is
146     keeping the test objects around, which is keeping it from destroying
147     filehandles, which is causing us to bomb out of a lot more tests than
148     necessary. Or, people could learn to close their files themselves.
149     either-or.
150 metson 1.22 """
151 metson 1.21 user_options = [ ]
152    
153     def initialize_options(self):
154     self._dir = os.getcwd()
155    
156     def finalize_options(self):
157     pass
158    
159     def run(self):
160     '''
161 metson 1.25 Finds all the tests modules in test/python/WMCore_t, and runs them.
162 metson 1.21 '''
163     testfiles = [ ]
164 metson 1.26
165 meloam 1.48 # attempt to perform coverage tests, even if they weren't asked for
166     # it doesn't cost (much), and by caching the results, a later
167     # coverage test run doesn't have to run through all the unittests
168     # just generate the coverage report
169     files = generate_filelist()
170     coverageEnabled = True;
171     try:
172     cov = coverage.coverage(branch = True, data_file="wmcore-coverage.dat" )
173     cov.start()
174     print "Caching code coverage statstics"
175     except:
176     coverageEnabled = False
177    
178 meloam 1.75 ## FIXME: make this more portable
179     if 'WMCOREBASE' not in os.environ:
180     os.environ['WMCOREBASE'] = os.getcwd()
181    
182 meloam 1.54 result, failedTestFiles, totalTests = runUnitTests()
183 meloam 1.48
184     if coverageEnabled:
185     cov.stop()
186     cov.save()
187    
188 meloam 1.54
189 metson 1.32 if not result.wasSuccessful():
190 meloam 1.44 print "Tests unsuccessful. There were %s failures and %s errors"\
191     % (len(result.failures), len(result.errors))
192     print "Failurelist:\n%s" % "\n".join(map(lambda x: \
193     "FAILURE: %s\n%s" % (x[0],x[1] ), result.failures))
194     print "Errorlist:\n%s" % "\n".join(map(lambda x: \
195     "ERROR: %s\n%s" % (x[0],x[1] ), result.errors))
196 meloam 1.54
197     if len(failedTestFiles):
198     print "The following tests failed to load: \n===============\n%s" %\
199     "\n".join(failedTestFiles)
200     print "------------------------------------"
201     print "test results"
202     print "------------------------------------"
203     print "Stats: %s successful, %s failures, %s errors, %s didn't run" %\
204     (totalTests - len(result.failures) - len(result.errors),\
205     len(result.failures),
206     len(result.errors),
207     len(failedTestFiles))
208 meloam 1.60
209 meloam 1.58 if (not result.wasSuccessful()) or len(result.errors):
210 meloam 1.44 print "FAILED: setup.py test"
211     sys.exit(1)
212     else:
213 meloam 1.54 print "PASS: setup.py test"
214 meloam 1.44 sys.exit(0)
215 meloam 1.54
216 meloam 1.44
217    
218    
219 metson 1.21 class CleanCommand(Command):
220 metson 1.49 description = "Clean up (delete) compiled files"
221 metson 1.21 user_options = [ ]
222    
223     def initialize_options(self):
224     self._clean_me = [ ]
225     for root, dirs, files in os.walk('.'):
226     for f in files:
227     if f.endswith('.pyc'):
228     self._clean_me.append(pjoin(root, f))
229    
230     def finalize_options(self):
231     pass
232    
233     def run(self):
234     for clean_me in self._clean_me:
235     try:
236     os.unlink(clean_me)
237     except:
238     pass
239    
240 metson 1.23 class LintCommand(Command):
241 metson 1.49 description = "Lint all files in the src tree"
242 metson 1.24 """
243     TODO: better format the test results, get some global result, make output
244     more buildbot friendly.
245     """
246    
247 metson 1.23 user_options = [ ]
248    
249     def initialize_options(self):
250     self._dir = os.getcwd()
251    
252     def finalize_options(self):
253     pass
254    
255     def run(self):
256     '''
257     Find the code and run lint on it
258     '''
259 metson 1.51 if can_lint:
260     srcpypath = '/'.join([self._dir, 'src/python/'])
261     sys.path.append(srcpypath)
262    
263     result = []
264     for filepath in generate_filelist():
265     result.append(lint_files([filepath]))
266     else:
267     print 'You need to install pylint before using the lint command'
268 metson 1.36
269     class ReportCommand(Command):
270 metson 1.49 description = "Generate a simple html report for ease of viewing in buildbot"
271 metson 1.36 """
272     To contain:
273     average lint score
274     % code coverage
275     list of classes missing tests
276     etc.
277     """
278    
279     user_options = [ ]
280    
281     def initialize_options(self):
282     pass
283    
284     def finalize_options(self):
285     pass
286    
287     def run(self):
288     """
289     run all the tests needed to generate the report and make an
290     html table
291     """
292     files = generate_filelist()
293    
294     error = 0
295     warning = 0
296     refactor = 0
297     convention = 0
298     statement = 0
299    
300     srcpypath = '/'.join([os.getcwd(), 'src/python/'])
301     sys.path.append(srcpypath)
302 metson 1.37
303     cfg = ConfigParser()
304     cfg.read('standards/.pylintrc')
305    
306 metson 1.40 # Supress stdout/stderr
307 metson 1.38 sys.stderr = open('/dev/null', 'w')
308 metson 1.39 sys.stdout = open('/dev/null', 'w')
309 meloam 1.47 # wrap it in an exception handler, otherwise we can't see why it fails
310     try:
311     # lint the code
312     for stats in lint_files(files):
313     error += stats['error']
314     warning += stats['warning']
315     refactor += stats['refactor']
316     convention += stats['convention']
317     statement += stats['statement']
318     except Exception,e:
319     # and restore the stdout/stderr
320     sys.stderr = sys.__stderr__
321     sys.stdout = sys.__stderr__
322     raise e
323 metson 1.37
324 metson 1.40 # and restore the stdout/stderr
325 meloam 1.47 sys.stderr = sys.__stderr__
326     sys.stdout = sys.__stderr__
327 metson 1.38
328 metson 1.37 stats = {'error': error,
329     'warning': warning,
330     'refactor': refactor,
331     'convention': convention,
332     'statement': statement}
333    
334     lint_score = eval(cfg.get('MASTER', 'evaluation'), {}, stats)
335 metson 1.36 coverage = 0 # TODO: calculate this
336     testless_classes = [] # TODO: generate this
337    
338     print "<table>"
339     print "<tr>"
340     print "<td colspan=2><h1>WMCore test report</h1></td>"
341     print "</tr>"
342     print "<tr>"
343     print "<td>Average lint score</td>"
344     print "<td>%.2f</td>" % lint_score
345     print "</tr>"
346     print "<tr>"
347     print "<td>% code coverage</td>"
348     print "<td>%s</td>" % coverage
349     print "</tr>"
350     print "<tr>"
351     print "<td>Classes missing tests</td>"
352     print "<td>"
353     if len(testless_classes) == 0:
354     print "None"
355     else:
356     print "<ul>"
357     for c in testless_classes:
358     print "<li>%c</li>" % c
359     print "</ul>"
360     print "</td>"
361     print "</tr>"
362     print "</table>"
363    
364     class CoverageCommand(Command):
365 metson 1.49 description = "Run code coverage tests"
366 metson 1.36 """
367 metson 1.49 To do this, we need to run all the unittests within the coverage
368     framework to record all the lines(and branches) executed
369 meloam 1.47 unfortunately, we have multiple code paths per database schema, so
370     we need to find a way to merge them.
371    
372     TODO: modify the test command to have a flag to record code coverage
373     the file thats used can then be used here, saving us from running
374     our tests twice
375 metson 1.36 """
376    
377     user_options = [ ]
378    
379     def initialize_options(self):
380     pass
381    
382     def finalize_options(self):
383     pass
384    
385     def run(self):
386     """
387     Determine the code's test coverage and return that as a float
388    
389     http://nedbatchelder.com/code/coverage/
390     """
391 metson 1.51 if can_coverage:
392     files = generate_filelist()
393     dataFile = None
394     cov = None
395 meloam 1.48
396 metson 1.51 # attempt to load previously cached coverage information if it exists
397     try:
398     dataFile = open("wmcore-coverage.dat","r")
399     cov = coverage.coverage(branch = True, data_file='wmcore-coverage.dat')
400     cov.load()
401     except:
402     cov = coverage.coverage(branch = True, )
403     cov.start()
404     runUnitTests()
405     cov.stop()
406     cov.save()
407    
408     # we have our coverage information, now let's do something with it
409     # get a list of modules
410     cov.report(morfs = files, file=sys.stdout)
411     return 0
412     else:
413     print 'You need the coverage module installed before running the' +\
414     ' coverage command'
415 metson 1.36
416 metson 1.41 class DumbCoverageCommand(Command):
417 metson 1.49 description = "Run a simple coverage test - find classes that don't have a unit test"
418 metson 1.41
419     user_options = [ ]
420    
421     def initialize_options(self):
422     pass
423    
424     def finalize_options(self):
425     pass
426    
427     def run(self):
428     """
429     Determine the code's test coverage in a dumb way and return that as a
430     float.
431     """
432 metson 1.42 print "This determines test coverage in a very crude manner. If your"
433     print "test file is incorrectly named it will not be counted, and"
434     print "result in a lower coverage score."
435 metson 1.43 print '----------------------------------------------------------------'
436 metson 1.42 filelist = generate_filelist()
437     tests = 0
438     files = 0
439     pkgcnt = 0
440 metson 1.41 dir = os.getcwd()
441 metson 1.42 pkg = {'name': '', 'files': 0, 'tests': 0}
442     for f in filelist:
443 metson 1.41 testpath = '/'.join([dir, f])
444     pth = testpath.split('./src/python/')
445     pth.append(pth[1].replace('/', '_t/').replace('.', '_t.'))
446 metson 1.42 if pkg['name'] == pth[2].rsplit('/', 1)[0].replace('_t/', '/'):
447     # pkg hasn't changed, increment counts
448     pkg['files'] += 1
449     else:
450     # new package, print stats for old package
451     pkgcnt += 1
452     if pkg['name'] != '' and pkg['files'] > 0:
453     print 'Package %s has coverage %.1f percent' % (pkg['name'],
454     (float(pkg['tests'])/float(pkg['files']) * 100))
455     # and start over for the new package
456     pkg['name'] = pth[2].rsplit('/', 1)[0].replace('_t/', '/')
457     # do global book keeping
458     files += pkg['files']
459     tests += pkg['tests']
460     pkg['files'] = 0
461     pkg['tests'] = 0
462 metson 1.41 pth[1] = 'test/python'
463     testpath = '/'.join(pth)
464 metson 1.42 try:
465     os.stat(testpath)
466     pkg['tests'] += 1
467 metson 1.41 except:
468 metson 1.42 pass
469    
470     coverage = (float(tests) / float(files)) * 100
471 metson 1.43 print '----------------------------------------------------------------'
472 metson 1.42 print 'Code coverage (%s packages) is %.2f percent' % (pkgcnt, coverage)
473 metson 1.41 return coverage
474 metson 1.49
475     class EnvCommand(Command):
476     description = "Configure the PYTHONPATH, DATABASE and PATH variables to" +\
477     "some sensible defaults, if not already set. Call with -q when eval-ing," +\
478 metson 1.50 """ e.g.:
479 metson 1.49 eval `python setup.py -q env`
480     """
481    
482     user_options = [ ]
483    
484     def initialize_options(self):
485     pass
486    
487     def finalize_options(self):
488     pass
489 metson 1.41
490 metson 1.49 def run(self):
491     if not os.getenv('DATABASE', False):
492     # Use an in memory sqlite one if none is configured.
493     print 'export DATABASE=sqlite://'
494    
495     here = os.path.abspath('.')
496     tests = here + '/test/python'
497     source = here + '/src/python'
498     webpth = source + '/WMCore/WebTools'
499    
500     pypath=os.getenv('PYTHONPATH', '').split(':')
501     for pth in [tests, source]:
502     if pth not in pypath:
503     pypath.append(pth)
504 metson 1.50
505     # We might want to add other executables to PATH
506 metson 1.49 expath=os.getenv('PATH', '').split(':')
507     for pth in [webpth]:
508     if pth not in expath:
509     expath.append(pth)
510    
511     print 'export PYTHONPATH=%s' % ':'.join(pypath)
512     print 'export PATH=%s' % ':'.join(expath)
513    
514 metson 1.22 def getPackages(package_dirs = []):
515     packages = []
516     for dir in package_dirs:
517     for dirpath, dirnames, filenames in os.walk('./%s' % dir):
518     # Exclude things here
519     if dirpath not in ['./src/python/', './src/python/IMProv']:
520     pathelements = dirpath.split('/')
521     if not 'CVS' in pathelements:
522     path = pathelements[3:]
523     packages.append('.'.join(path))
524     return packages
525    
526     package_dir = {'WMCore': 'src/python/WMCore',
527     'WMComponent' : 'src/python/WMComponent',
528     'WMQuality' : 'src/python/WMQuality'}
529    
530     setup (name = 'wmcore',
531     version = '1.0',
532 metson 1.36 maintainer_email = 'hn-cms-wmDevelopment@cern.ch',
533     cmdclass = {'test': TestCommand,
534 metson 1.23 'clean': CleanCommand,
535 metson 1.36 'lint': LintCommand,
536     'report': ReportCommand,
537 metson 1.41 'coverage': CoverageCommand ,
538 metson 1.49 'missing': DumbCoverageCommand,
539     'env': EnvCommand },
540 metson 1.22 package_dir = package_dir,
541     packages = getPackages(package_dir.values()),)
542 fvlingen 1.7