| OLD | NEW |
| (Empty) |
| 1 | |
| 2 from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS | |
| 3 from buildbot.steps.shell import ShellCommand | |
| 4 import re | |
| 5 | |
| 6 try: | |
| 7 import cStringIO | |
| 8 StringIO = cStringIO.StringIO | |
| 9 except ImportError: | |
| 10 from StringIO import StringIO | |
| 11 | |
| 12 | |
| 13 class BuildEPYDoc(ShellCommand): | |
| 14 name = "epydoc" | |
| 15 command = ["make", "epydocs"] | |
| 16 description = ["building", "epydocs"] | |
| 17 descriptionDone = ["epydoc"] | |
| 18 | |
| 19 def createSummary(self, log): | |
| 20 import_errors = 0 | |
| 21 warnings = 0 | |
| 22 errors = 0 | |
| 23 | |
| 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 | |
| 31 | |
| 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) | |
| 39 | |
| 40 self.import_errors = import_errors | |
| 41 self.warnings = warnings | |
| 42 self.errors = errors | |
| 43 | |
| 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 | |
| 50 | |
| 51 | |
| 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 | |
| 59 | |
| 60 MESSAGES = ("unused", "undefined", "redefs", "import*", "misc") | |
| 61 | |
| 62 def createSummary(self, log): | |
| 63 counts = {} | |
| 64 summaries = {} | |
| 65 for m in self.MESSAGES: | |
| 66 counts[m] = 0 | |
| 67 summaries[m] = [] | |
| 68 | |
| 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 | |
| 94 | |
| 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") | |
| 102 | |
| 103 | |
| 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 | |
| 113 | |
| 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. | |
| 118 ''' | |
| 119 name = "pylint" | |
| 120 description = ["running", "pylint"] | |
| 121 descriptionDone = ["pylint"] | |
| 122 | |
| 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: | |
| 129 | |
| 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", | |
| 137 } | |
| 138 | |
| 139 flunkingIssues = ["F", "E"] # msg categories that cause FAILURE | |
| 140 | |
| 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) | |
| 145 | |
| 146 def createSummary(self, log): | |
| 147 counts = {} | |
| 148 summaries = {} | |
| 149 for m in self.MESSAGES: | |
| 150 counts[m] = 0 | |
| 151 summaries[m] = [] | |
| 152 | |
| 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 | |
| 169 | |
| 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())) | |
| 177 | |
| 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 | |
| 187 | |
| OLD | NEW |