ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WMCORE/setup.py
Revision: 1.64
Committed: Thu Jan 14 07:31:15 2010 UTC (15 years, 3 months ago) by meloam
Content type: text/x-python
Branch: MAIN
CVS Tags: WMCORE_0_1_1_pre18, WMCORE_T0_0_0_4_pre10
Changes since 1.63: +1 -2 lines
Log Message:
make setup.py more verbose when running tests. Also, this commit should interrupt buildbot from doing a full test. let's see.

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