(closes #576) use a list comp, not a generator, for py2.3
[buildbot.git] / buildbot / steps / python.py
blob7f87aa76c785b6b8411aa3b44753850b288b8f40
2 from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS
3 from buildbot.steps.shell import ShellCommand
4 import re
6 try:
7 import cStringIO
8 StringIO = cStringIO.StringIO
9 except ImportError:
10 from StringIO import StringIO
13 class BuildEPYDoc(ShellCommand):
14 name = "epydoc"
15 command = ["make", "epydocs"]
16 description = ["building", "epydocs"]
17 descriptionDone = ["epydoc"]
19 def createSummary(self, log):
20 import_errors = 0
21 warnings = 0
22 errors = 0
24 for line in StringIO(log.getText()):
25 if line.startswith("Error importing "):
26 import_errors += 1
27 if line.find("Warning: ") != -1:
28 warnings += 1
29 if line.find("Error: ") != -1:
30 errors += 1
32 self.descriptionDone = self.descriptionDone[:]
33 if import_errors:
34 self.descriptionDone.append("ierr=%d" % import_errors)
35 if warnings:
36 self.descriptionDone.append("warn=%d" % warnings)
37 if errors:
38 self.descriptionDone.append("err=%d" % errors)
40 self.import_errors = import_errors
41 self.warnings = warnings
42 self.errors = errors
44 def evaluateCommand(self, cmd):
45 if cmd.rc != 0:
46 return FAILURE
47 if self.warnings or self.errors:
48 return WARNINGS
49 return SUCCESS
52 class PyFlakes(ShellCommand):
53 name = "pyflakes"
54 command = ["make", "pyflakes"]
55 description = ["running", "pyflakes"]
56 descriptionDone = ["pyflakes"]
57 flunkOnFailure = False
58 flunkingIssues = ["undefined"] # any pyflakes lines like this cause FAILURE
60 MESSAGES = ("unused", "undefined", "redefs", "import*", "misc")
62 def createSummary(self, log):
63 counts = {}
64 summaries = {}
65 for m in self.MESSAGES:
66 counts[m] = 0
67 summaries[m] = []
69 first = True
70 for line in StringIO(log.getText()).readlines():
71 # the first few lines might contain echoed commands from a 'make
72 # pyflakes' step, so don't count these as warnings. Stop ignoring
73 # the initial lines as soon as we see one with a colon.
74 if first:
75 if line.find(":") != -1:
76 # there's the colon, this is the first real line
77 first = False
78 # fall through and parse the line
79 else:
80 # skip this line, keep skipping non-colon lines
81 continue
82 if line.find("imported but unused") != -1:
83 m = "unused"
84 elif line.find("*' used; unable to detect undefined names") != -1:
85 m = "import*"
86 elif line.find("undefined name") != -1:
87 m = "undefined"
88 elif line.find("redefinition of unused") != -1:
89 m = "redefs"
90 else:
91 m = "misc"
92 summaries[m].append(line)
93 counts[m] += 1
95 self.descriptionDone = self.descriptionDone[:]
96 for m in self.MESSAGES:
97 if counts[m]:
98 self.descriptionDone.append("%s=%d" % (m, counts[m]))
99 self.addCompleteLog(m, "".join(summaries[m]))
100 self.setProperty("pyflakes-%s" % m, counts[m], "pyflakes")
101 self.setProperty("pyflakes-total", sum(counts.values()), "pyflakes")
104 def evaluateCommand(self, cmd):
105 if cmd.rc != 0:
106 return FAILURE
107 for m in self.flunkingIssues:
108 if self.getProperty("pyflakes-%s" % m):
109 return FAILURE
110 if self.getProperty("pyflakes-total"):
111 return WARNINGS
112 return SUCCESS
114 class PyLint(ShellCommand):
115 '''A command that knows about pylint output.
116 It's a good idea to add --output-format=parseable to your
117 command, since it includes the filename in the message.
119 name = "pylint"
120 description = ["running", "pylint"]
121 descriptionDone = ["pylint"]
123 # Using the default text output, the message format is :
124 # MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
125 # with --output-format=parseable it is: (the outer brackets are literal)
126 # FILE_NAME:LINE_NUM: [MESSAGE_TYPE[, OBJECT]] MESSAGE
127 # message type consists of the type char and 4 digits
128 # The message types:
130 MESSAGES = {
131 'C': "convention", # for programming standard violation
132 'R': "refactor", # for bad code smell
133 'W': "warning", # for python specific problems
134 'E': "error", # for much probably bugs in the code
135 'F': "fatal", # error prevented pylint from further processing.
136 'I': "info",
139 flunkingIssues = ["F", "E"] # msg categories that cause FAILURE
141 _re_groupname = 'errtype'
142 _msgtypes_re_str = '(?P<%s>[%s])' % (_re_groupname, ''.join(MESSAGES.keys()))
143 _default_line_re = re.compile(r'%s\d{4}: *\d+:.+' % _msgtypes_re_str)
144 _parseable_line_re = re.compile(r'[^:]+:\d+: \[%s\d{4}[,\]] .+' % _msgtypes_re_str)
146 def createSummary(self, log):
147 counts = {}
148 summaries = {}
149 for m in self.MESSAGES:
150 counts[m] = 0
151 summaries[m] = []
153 line_re = None # decide after first match
154 for line in StringIO(log.getText()).readlines():
155 if not line_re:
156 # need to test both and then decide on one
157 if self._parseable_line_re.match(line):
158 line_re = self._parseable_line_re
159 elif self._default_line_re.match(line):
160 line_re = self._default_line_re
161 else: # no match yet
162 continue
163 mo = line_re.match(line)
164 if mo:
165 msgtype = mo.group(self._re_groupname)
166 assert msgtype in self.MESSAGES
167 summaries[msgtype].append(line)
168 counts[msgtype] += 1
170 self.descriptionDone = self.descriptionDone[:]
171 for msg, fullmsg in self.MESSAGES.items():
172 if counts[msg]:
173 self.descriptionDone.append("%s=%d" % (fullmsg, counts[msg]))
174 self.addCompleteLog(fullmsg, "".join(summaries[msg]))
175 self.setProperty("pylint-%s" % fullmsg, counts[msg])
176 self.setProperty("pylint-total", sum(counts.values()))
178 def evaluateCommand(self, cmd):
179 if cmd.rc != 0:
180 return FAILURE
181 for msg in self.flunkingIssues:
182 if self.getProperty("pylint-%s" % self.MESSAGES[msg]):
183 return FAILURE
184 if self.getProperty("pylint-total"):
185 return WARNINGS
186 return SUCCESS