ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/WMCORE/setup.py
Revision: 1.46
Committed: Wed Dec 16 02:55:32 2009 UTC (15 years, 4 months ago) by meloam
Content type: text/x-python
Branch: MAIN
Changes since 1.45: +12 -1 lines
Log Message:
give a better explanation if someone tries to run lint without having lint installed.

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 metson 1.30 import os, sys
8 metson 1.38
9 sfoulkes 1.29 try:
10     from pylint import lint
11 metson 1.36 #PyLinter
12 sfoulkes 1.29 except:
13     pass
14 fvlingen 1.1
15 metson 1.22 """
16     Build, clean and test the WMCore package.
17     """
18    
19 metson 1.36 def generate_filelist():
20     files = []
21     for dirpath, dirnames, filenames in os.walk('./src/python/'):
22     # skipping CVS directories and their contents
23     pathelements = dirpath.split('/')
24     result = []
25     if not 'CVS' in pathelements:
26     # to build up a list of file names which contain tests
27     for file in filenames:
28     if file.endswith('.py'):
29     filepath = '/'.join([dirpath, file])
30     files.append(filepath)
31     return files
32    
33     def lint_files(files):
34     """
35     lint a (list of) file(s) and return the results
36     """
37     input = ['--rcfile=standards/.pylintrc',
38     '--output-format=parseable',
39     '--reports=n', ]
40     input.extend(files)
41 meloam 1.46 try:
42     lint_result = lint.Run(input)
43     except NameError:
44     print "In order to run lint, you must have pylint installed"
45     print "lint failure."
46     sys.exit(1)
47 metson 1.36 return lint_result.linter.stats
48    
49 metson 1.21 class TestCommand(Command):
50 metson 1.22 """
51     Handle setup.py test with this class - walk through the directory structure
52     and build up a list of tests, then build a test suite and execute it.
53    
54     TODO: Pull database URL's from environment, and skip tests where database
55     URL is not present (e.g. for a slave without Oracle connection)
56 meloam 1.46
57     TODO: need to build a separate test runner for each test file, python is
58     keeping the test objects around, which is keeping it from destroying
59     filehandles, which is causing us to bomb out of a lot more tests than
60     necessary. Or, people could learn to close their files themselves.
61     either-or.
62 metson 1.22 """
63 metson 1.21 user_options = [ ]
64    
65     def initialize_options(self):
66     self._dir = os.getcwd()
67    
68     def finalize_options(self):
69     pass
70    
71     def run(self):
72     '''
73 metson 1.25 Finds all the tests modules in test/python/WMCore_t, and runs them.
74 metson 1.21 '''
75     testfiles = [ ]
76 metson 1.26
77     # Add the test and src directory to the python path
78     testspypath = '/'.join([self._dir, 'test/python/'])
79     srcpypath = '/'.join([self._dir, 'src/python/'])
80 metson 1.30 sys.path.append(testspypath)
81     sys.path.append(srcpypath)
82    
83 metson 1.21 # Walk the directory tree
84     for dirpath, dirnames, filenames in os.walk('./test/python/WMCore_t'):
85     # skipping CVS directories and their contents
86     pathelements = dirpath.split('/')
87     if not 'CVS' in pathelements:
88     # to build up a list of file names which contain tests
89     for file in filenames:
90     if file not in ['__init__.py']:
91     if file.endswith('_t.py'):
92     testmodpath = pathelements[3:]
93     testmodpath.append(file.replace('.py',''))
94     testfiles.append('.'.join(testmodpath))
95    
96 meloam 1.44 oldstdout = sys.stdout
97     oldstderr = sys.stderr
98    
99     sys.stdout = None
100    
101 metson 1.21 testsuite = TestSuite()
102 meloam 1.44 failedTestFiles = []
103 metson 1.21 for test in testfiles:
104     try:
105     testsuite.addTest(TestLoader().loadTestsFromName(test))
106     except Exception, e:
107 meloam 1.44 failedTestFiles.append(test)
108     #print "Could not load %s test - fix it!\n %s" % (test, e)
109     #print "Running %s tests" % testsuite.countTestCases()
110 metson 1.21
111     t = TextTestRunner(verbosity = 1)
112 metson 1.32 result = t.run(testsuite)
113 meloam 1.44 sys.stdout = oldstdout
114     sys.stderr = oldstderr
115 metson 1.32 if not result.wasSuccessful():
116 meloam 1.44 print "Tests unsuccessful. There were %s failures and %s errors"\
117     % (len(result.failures), len(result.errors))
118     print "Failurelist:\n%s" % "\n".join(map(lambda x: \
119     "FAILURE: %s\n%s" % (x[0],x[1] ), result.failures))
120     print "Errorlist:\n%s" % "\n".join(map(lambda x: \
121     "ERROR: %s\n%s" % (x[0],x[1] ), result.errors))
122     if len(failedTestFiles):
123     print "The following tests failed to load: \n===============\n%s" %\
124     "\n".join(failedTestFiles)
125     #"".join(' ', [result.failures[0],result.failures[1]]))
126     #print "Errorlist:\n%s" % result.errors #"".join(' ', [result.errors[0],result.errors[1]]))
127 meloam 1.45 print "Tests unsuccessful. There were %s failures and %s errors"\
128     % (len(result.failures), len(result.errors))
129 meloam 1.44 print "FAILED: setup.py test"
130     sys.exit(1)
131     else:
132     print "Tests successful"
133     if len(failedTestFiles):
134     print "The following tests failed to load: \n===============\n%s" %\
135     "\n".join(failedTestFiles)
136     print "PASSED: setup.py test"
137     sys.exit(0)
138 metson 1.32
139 meloam 1.44
140    
141    
142 metson 1.21 class CleanCommand(Command):
143 metson 1.22 """
144     Clean up (delete) compiled files
145     """
146 metson 1.21 user_options = [ ]
147    
148     def initialize_options(self):
149     self._clean_me = [ ]
150     for root, dirs, files in os.walk('.'):
151     for f in files:
152     if f.endswith('.pyc'):
153     self._clean_me.append(pjoin(root, f))
154    
155     def finalize_options(self):
156     pass
157    
158     def run(self):
159     for clean_me in self._clean_me:
160     try:
161     os.unlink(clean_me)
162     except:
163     pass
164    
165 metson 1.23 class LintCommand(Command):
166 metson 1.24 """
167     Lint all files in the src tree
168    
169     TODO: better format the test results, get some global result, make output
170     more buildbot friendly.
171     """
172    
173 metson 1.23 user_options = [ ]
174    
175     def initialize_options(self):
176     self._dir = os.getcwd()
177    
178     def finalize_options(self):
179     pass
180    
181     def run(self):
182     '''
183     Find the code and run lint on it
184     '''
185 metson 1.26 srcpypath = '/'.join([self._dir, 'src/python/'])
186 metson 1.30 sys.path.append(srcpypath)
187 metson 1.26
188 metson 1.36 result = []
189     for filepath in generate_filelist():
190     result.append(lint_files([filepath]))
191    
192     class ReportCommand(Command):
193     """
194     Generate a simple html report for ease of viewing in buildbot
195    
196     To contain:
197     average lint score
198     % code coverage
199     list of classes missing tests
200     etc.
201     """
202    
203     user_options = [ ]
204    
205     def initialize_options(self):
206     pass
207    
208     def finalize_options(self):
209     pass
210    
211     def run(self):
212     """
213     run all the tests needed to generate the report and make an
214     html table
215     """
216     files = generate_filelist()
217    
218     error = 0
219     warning = 0
220     refactor = 0
221     convention = 0
222     statement = 0
223    
224     srcpypath = '/'.join([os.getcwd(), 'src/python/'])
225     sys.path.append(srcpypath)
226 metson 1.37
227     cfg = ConfigParser()
228     cfg.read('standards/.pylintrc')
229    
230 metson 1.40 # Supress stdout/stderr
231 metson 1.39 err_bak = sys.stderr
232     out_bak = sys.stdout
233 metson 1.38 sys.stderr = open('/dev/null', 'w')
234 metson 1.39 sys.stdout = open('/dev/null', 'w')
235    
236 metson 1.40 # lint the code
237 metson 1.36 for stats in lint_files(files):
238     error += stats['error']
239     warning += stats['warning']
240     refactor += stats['refactor']
241     convention += stats['convention']
242     statement += stats['statement']
243 metson 1.37
244 metson 1.40 # and restore the stdout/stderr
245 metson 1.39 sys.stderr = err_bak
246     sys.stdout = out_bak
247 metson 1.38
248 metson 1.37 stats = {'error': error,
249     'warning': warning,
250     'refactor': refactor,
251     'convention': convention,
252     'statement': statement}
253    
254     lint_score = eval(cfg.get('MASTER', 'evaluation'), {}, stats)
255 metson 1.36 coverage = 0 # TODO: calculate this
256     testless_classes = [] # TODO: generate this
257    
258     print "<table>"
259     print "<tr>"
260     print "<td colspan=2><h1>WMCore test report</h1></td>"
261     print "</tr>"
262     print "<tr>"
263     print "<td>Average lint score</td>"
264     print "<td>%.2f</td>" % lint_score
265     print "</tr>"
266     print "<tr>"
267     print "<td>% code coverage</td>"
268     print "<td>%s</td>" % coverage
269     print "</tr>"
270     print "<tr>"
271     print "<td>Classes missing tests</td>"
272     print "<td>"
273     if len(testless_classes) == 0:
274     print "None"
275     else:
276     print "<ul>"
277     for c in testless_classes:
278     print "<li>%c</li>" % c
279     print "</ul>"
280     print "</td>"
281     print "</tr>"
282     print "</table>"
283    
284     class CoverageCommand(Command):
285     """
286     Run code coverage tests
287     """
288    
289     user_options = [ ]
290    
291     def initialize_options(self):
292     pass
293    
294     def finalize_options(self):
295     pass
296    
297     def run(self):
298     """
299     Determine the code's test coverage and return that as a float
300    
301     http://nedbatchelder.com/code/coverage/
302     """
303     return 0.0
304    
305 metson 1.41 class DumbCoverageCommand(Command):
306     """
307     Run a simple coverage test - find classes that don't have a unit test
308     """
309    
310     user_options = [ ]
311    
312     def initialize_options(self):
313     pass
314    
315     def finalize_options(self):
316     pass
317    
318     def run(self):
319     """
320     Determine the code's test coverage in a dumb way and return that as a
321     float.
322     """
323 metson 1.42 print "This determines test coverage in a very crude manner. If your"
324     print "test file is incorrectly named it will not be counted, and"
325     print "result in a lower coverage score."
326 metson 1.43 print '----------------------------------------------------------------'
327 metson 1.42 filelist = generate_filelist()
328     tests = 0
329     files = 0
330     pkgcnt = 0
331 metson 1.41 dir = os.getcwd()
332 metson 1.42 pkg = {'name': '', 'files': 0, 'tests': 0}
333     for f in filelist:
334 metson 1.41 testpath = '/'.join([dir, f])
335     pth = testpath.split('./src/python/')
336     pth.append(pth[1].replace('/', '_t/').replace('.', '_t.'))
337 metson 1.42 if pkg['name'] == pth[2].rsplit('/', 1)[0].replace('_t/', '/'):
338     # pkg hasn't changed, increment counts
339     pkg['files'] += 1
340     else:
341     # new package, print stats for old package
342     pkgcnt += 1
343     if pkg['name'] != '' and pkg['files'] > 0:
344     print 'Package %s has coverage %.1f percent' % (pkg['name'],
345     (float(pkg['tests'])/float(pkg['files']) * 100))
346     # and start over for the new package
347     pkg['name'] = pth[2].rsplit('/', 1)[0].replace('_t/', '/')
348     # do global book keeping
349     files += pkg['files']
350     tests += pkg['tests']
351     pkg['files'] = 0
352     pkg['tests'] = 0
353 metson 1.41 pth[1] = 'test/python'
354     testpath = '/'.join(pth)
355 metson 1.42 try:
356     os.stat(testpath)
357     pkg['tests'] += 1
358 metson 1.41 except:
359 metson 1.42 pass
360    
361     coverage = (float(tests) / float(files)) * 100
362 metson 1.43 print '----------------------------------------------------------------'
363 metson 1.42 print 'Code coverage (%s packages) is %.2f percent' % (pkgcnt, coverage)
364 metson 1.41 return coverage
365    
366 metson 1.22 def getPackages(package_dirs = []):
367     packages = []
368     for dir in package_dirs:
369     for dirpath, dirnames, filenames in os.walk('./%s' % dir):
370     # Exclude things here
371     if dirpath not in ['./src/python/', './src/python/IMProv']:
372     pathelements = dirpath.split('/')
373     if not 'CVS' in pathelements:
374     path = pathelements[3:]
375     packages.append('.'.join(path))
376     return packages
377    
378     package_dir = {'WMCore': 'src/python/WMCore',
379     'WMComponent' : 'src/python/WMComponent',
380     'WMQuality' : 'src/python/WMQuality'}
381    
382     setup (name = 'wmcore',
383     version = '1.0',
384 metson 1.36 maintainer_email = 'hn-cms-wmDevelopment@cern.ch',
385     cmdclass = {'test': TestCommand,
386 metson 1.23 'clean': CleanCommand,
387 metson 1.36 'lint': LintCommand,
388     'report': ReportCommand,
389 metson 1.41 'coverage': CoverageCommand ,
390     'missing': DumbCoverageCommand },
391 metson 1.22 package_dir = package_dir,
392     packages = getPackages(package_dir.values()),)
393 fvlingen 1.7