ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WMCORE/setup.py
Revision: 1.65
Committed: Sat Jan 16 20:19:38 2010 UTC (15 years, 3 months ago) by meloam
Content type: text/x-python
Branch: MAIN
Changes since 1.64: +10 -3 lines
Log Message:
there is, apparently, a difference between addTest() and addTests(). A LOT of tests weren't running because of this.

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