Difference between revisions of "Mozilla BuildBot Trending/Snippets"
(New page: ==Date Format Parser== This js code parses a terminal date string into a real date and time. <pre> // This file is a parser for terminal "date" command // format strings. Likely, you wil...) |
|||
Line 1: | Line 1: | ||
+ | ==Log File Parser== | ||
+ | <pre> | ||
+ | #!/usr/bin/env python | ||
+ | """This is a log parser for Mozilla Testing Logs | ||
+ | Written by John Ford in 2009 and made available under MPL/GPL/LGPL""" | ||
+ | |||
+ | import re | ||
+ | from sys import argv | ||
+ | |||
+ | class Log: | ||
+ | """I represent a Log. Currently, I only understand how to | ||
+ | parse lines of test output. I do not yet know about myself""" | ||
+ | lines = [] #All LogLine objects | ||
+ | def __init__(self, filename): | ||
+ | """Create a Log object based on the file at a location""" | ||
+ | print "Log file: %s" % filename | ||
+ | try: | ||
+ | f = open(filename) | ||
+ | self.parseFile(f) | ||
+ | except IOError: | ||
+ | print "Log: encountered error while reading file" | ||
+ | finally: | ||
+ | f.close() | ||
+ | |||
+ | def parseFile(self, file): | ||
+ | """I take a file object and parse all of its lines. Currently, | ||
+ | I understand most standard XPCShell tests, feed tests, and Reftests""" | ||
+ | rawlines = file.readlines() | ||
+ | for line in rawlines: | ||
+ | ############################### | ||
+ | # Special case for feed tests # | ||
+ | ############################### | ||
+ | if(re.match(r"PASS \| xml", line) or re.match(r"FAIL \| xml", line)): | ||
+ | try: | ||
+ | self.lines.append(FeedTestLogLine(line)) | ||
+ | except InvalidLogLineError: | ||
+ | print "This line is not a feed test but shows up as such:\n%s" % line | ||
+ | ############################# | ||
+ | # Special case for reftests # | ||
+ | ############################# | ||
+ | elif(re.search(r"REFTEST", line)): | ||
+ | try: | ||
+ | self.lines.append(ReftestLogLine(line)) | ||
+ | except InvalidLogLineError: | ||
+ | print "This line is not a reftest but shows up as such:\n%s" % line | ||
+ | ############################### | ||
+ | # Standard test output format # | ||
+ | ############################### | ||
+ | elif(re.search(r"PASS", line) or re.search(r"FAIL", line) or re.search(r"EXPECTED RANDOM", line)): | ||
+ | try: | ||
+ | self.lines.append(TestLogLine(line)) | ||
+ | except InvalidLogLineError: | ||
+ | # TODO: figure out why this line isn't being executed ever | ||
+ | print "This line is not a standard test but shows up as such:\n%s" % line | ||
+ | ################################################################ | ||
+ | # Make sure that the start time is captured for the feed tests # | ||
+ | ################################################################ | ||
+ | # This is a bad way of doing this but is there until timestamps | ||
+ | # are added | ||
+ | elif(re.match(r"Start: ", line)): | ||
+ | time = line.replace("Start: ", "").strip() | ||
+ | for x in self.lines: | ||
+ | if(isinstance(x, FeedTestLogLine)): | ||
+ | x.startTime = time | ||
+ | |||
+ | |||
+ | |||
+ | def buildNumber(self): | ||
+ | """I tell you which Build Number I am""" | ||
+ | return 1 | ||
+ | def startTime(self): | ||
+ | """I tell you when I started""" | ||
+ | return 1 | ||
+ | def endTime(self): | ||
+ | """I tell you when I finished""" | ||
+ | return 10 | ||
+ | def factory(self): | ||
+ | """I tell you which buildbot Factory I came from""" | ||
+ | return "f1" | ||
+ | def builder(self): | ||
+ | """I tell you which buildbot Builder I came from""" | ||
+ | return "mozilla-buildbot" | ||
+ | def slave(self): | ||
+ | """I tell you which buildbot BuildSlave I came from""" | ||
+ | return "local-osx" | ||
+ | def testCount(self): | ||
+ | """I tell you how many test's output I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def passCount(self): | ||
+ | """I tell you how many test passes I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.passed()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def failCount(self): | ||
+ | """I tell you how many test fails I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.failed()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def unexpectedCount(self): | ||
+ | """I tell you how many unexpected test results I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.unexpected()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def unexpectedPassCount(self): | ||
+ | """I tell you how many unexpected passes I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.unexpectedPass()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def unexpectedFailCount(self): | ||
+ | """I tell you how many unexpected passes I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.unexpectedFail()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def knownFailCount(self): | ||
+ | """I tell you how many known fails I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.knownFail()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def randomCount(self): | ||
+ | """I tell you how many known fails I found""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.random()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def badThingsCount(self): | ||
+ | """I tell you how many BadThings happend""" | ||
+ | count = 0 | ||
+ | for line in self.lines: | ||
+ | if isinstance(line, TestLogLine): | ||
+ | if(line.unexpected() or line.fail()): | ||
+ | count = count + 1 | ||
+ | return count | ||
+ | def logURL(self): | ||
+ | """I tell you where you can find me""" | ||
+ | return "http://www.example.com/waterfall" | ||
+ | def logLines(self): | ||
+ | """If you want my LogLines, ask me here""" | ||
+ | return self.lines | ||
+ | |||
+ | class LogLine: | ||
+ | """I represent a Log Line. I might be more than one | ||
+ | line of raw output from the log. I understand log output | ||
+ | in the format "X | msg" where X is a time and msg is | ||
+ | the rest of the output""" | ||
+ | |||
+ | startTime = None # Time command starts | ||
+ | msg = None # Message | ||
+ | |||
+ | def __init__(self, line): | ||
+ | chunks = line.split(" | ") | ||
+ | self.startTime = chunks[0].strip() | ||
+ | self.msg = chunks[1].strip() | ||
+ | def time(self): | ||
+ | """I Tell you the time it took to make this line""" | ||
+ | return "%s minus %s" % startTime, endTime | ||
+ | #def startTime(self): | ||
+ | #"""I tell you the time this item was started""" | ||
+ | #return startTime | ||
+ | def endTime(self): | ||
+ | """I tell you the time this line ended UNIMPLEMENTED""" | ||
+ | return None | ||
+ | |||
+ | class TestLogLine(LogLine): | ||
+ | """I am a line of output specific to a Test run. | ||
+ | I understand logs in the format "X | Y | Z | Q" | ||
+ | Where X is a timestamp, Y is test status, Z is | ||
+ | a test name and Q is the test message""" | ||
+ | |||
+ | statusMsg = None # I store PASS, FAIL.... | ||
+ | testName = None # I store the name of the test | ||
+ | |||
+ | def __init__(self, line): | ||
+ | """Parse a line into a TestLogLine output""" | ||
+ | chunks = line.split(" | ") | ||
+ | try: | ||
+ | self.startTime = chunks[0].strip() | ||
+ | self.statusMsg = chunks[1].strip() | ||
+ | self.testName = chunks[2].strip() | ||
+ | self.msg = chunks[3].strip() | ||
+ | except IndexError: | ||
+ | raise InvalidLogLineError("This line does not use the standard log output format") | ||
+ | |||
+ | def passed(self): | ||
+ | """I tell you if this line passed""" | ||
+ | return re.search("PASS", self.statusMsg) != None | ||
+ | def failed(self): | ||
+ | """I tell you if this line failed""" | ||
+ | return re.search("FAIL", self.statusMsg) != None | ||
+ | def knownFail(self): | ||
+ | """I tell you if this test was a known fail""" | ||
+ | return re.search("KNOWN-FAIL", self.statusMsg) | ||
+ | def unexpected(self): | ||
+ | """I tell you if this was an unexpected result""" | ||
+ | return re.search("UNEXPECTED", self.statusMsg) != None | ||
+ | def unexpectedPass(self): | ||
+ | """I tell you if this was an unexpected pass""" | ||
+ | return self.passed() and self.unexpected() | ||
+ | def unexpectedFail(self): | ||
+ | """I tell you if this was an unexpected fail""" | ||
+ | return self.failed() and self.unexpected() | ||
+ | def random(self): | ||
+ | """I tell you if this was expected random output""" | ||
+ | return re.search(r"\(EXPECTED RANDOM\)", self.statusMsg) | ||
+ | |||
+ | class FeedTestLogLine(TestLogLine): | ||
+ | """I am a special case for the feed tests. I don't have | ||
+ | timestamps in my output yet, so the Log's parsefile method | ||
+ | adds the same timestamp to all of us. I understand log output | ||
+ | in the form: X | Y | Z | Q where X is test status, | ||
+ | Y and Z are the test name and Q is the test outcome""" | ||
+ | def __init__(self, line): | ||
+ | chunks = line.split(" | ") | ||
+ | try: | ||
+ | self.startTime = repr(0) | ||
+ | self.statusMsg = chunks[0].strip() | ||
+ | self.testName = "%s - %s" % (chunks[1].strip(), chunks[2].strip()) | ||
+ | self.msg = chunks[3].strip() | ||
+ | except IndexError: | ||
+ | raise InvalidLogLineError("This line does not use the standard log output format") | ||
+ | |||
+ | class ReftestLogLine(TestLogLine): | ||
+ | """I am a line of Reftest output. I do not yet have timing information | ||
+ | for each step and take a really long time to run so the suite's start time | ||
+ | is not valid for me. I understand things in the format X | Y | Z where | ||
+ | X is test status (with the word REFTEST removed), Y is the test's name and | ||
+ | Z is any special messages""" | ||
+ | def __init__(self, line): | ||
+ | chunks = line.split(" | ") | ||
+ | try: | ||
+ | self.startTime = repr(0) | ||
+ | self.statusMsg = chunks[0].replace("REFTEST ", "").strip() | ||
+ | self.testName = chunks[1].strip() | ||
+ | try: | ||
+ | self.msg = chunks[2].strip() | ||
+ | except IndexError: | ||
+ | self.msg = "No Message" | ||
+ | except IndexError: | ||
+ | raise InvalidLogLineError("This Reftest does not use the standard log output format") | ||
+ | |||
+ | |||
+ | class InvalidLogLineError(Exception): | ||
+ | """I am an exception which is raised when someone makes an invalid LogLine""" | ||
+ | pass | ||
+ | |||
+ | |||
+ | if(__name__ == "__main__"): | ||
+ | log = Log(argv[1]) | ||
+ | # This is desired output format from Bug443329 | ||
+ | # BUILDID/BUILDTIME | TESTTIME | TESTNAME | MACHINE(from this we can map to OS) | STATUS | MSG | link to full log | ||
+ | for line in log.logLines(): | ||
+ | if (isinstance(line, TestLogLine)): | ||
+ | print "%s/%s | %s | %s | %s | %s | %s | %s" % ( | ||
+ | log.buildNumber(), | ||
+ | repr(log.startTime()), | ||
+ | line.startTime, | ||
+ | line.testName, | ||
+ | log.slave(), | ||
+ | line.statusMsg, | ||
+ | line.msg, | ||
+ | log.logURL()) | ||
+ | print """Note: | ||
+ | Build ID, Build Time, Machine Name, Link to log are | ||
+ | all bogus fields""" | ||
+ | |||
+ | """ This code is what i tested with | ||
+ | for line in log.logLines(): | ||
+ | if(isinstance(line, TestLogLine)): | ||
+ | print "=" * 100 | ||
+ | print "Line start: %s" % line.startTime | ||
+ | print "Line status: %s" % line.statusMsg | ||
+ | print "Line name: %s" % line.testName | ||
+ | print "Line Msg: %s" % line.msg | ||
+ | print "Count: %d" % log.testCount() | ||
+ | print "Fails: %d" % log.failCount() | ||
+ | print "Known Fails: %d" % log.knownFailCount() | ||
+ | print "Passes: %d" % log.passCount() | ||
+ | print "Unexpected: %d" % log.unexpectedCount() | ||
+ | print "UnexpectedP: %d" % log.unexpectedPassCount() | ||
+ | print "UnexpectedF: %d" % log.unexpectedFailCount() | ||
+ | print "Random: %d" % log.randomCount()""" | ||
+ | </pre> | ||
==Date Format Parser== | ==Date Format Parser== |
Latest revision as of 23:09, 22 February 2009
Log File Parser
#!/usr/bin/env python """This is a log parser for Mozilla Testing Logs Written by John Ford in 2009 and made available under MPL/GPL/LGPL""" import re from sys import argv class Log: """I represent a Log. Currently, I only understand how to parse lines of test output. I do not yet know about myself""" lines = [] #All LogLine objects def __init__(self, filename): """Create a Log object based on the file at a location""" print "Log file: %s" % filename try: f = open(filename) self.parseFile(f) except IOError: print "Log: encountered error while reading file" finally: f.close() def parseFile(self, file): """I take a file object and parse all of its lines. Currently, I understand most standard XPCShell tests, feed tests, and Reftests""" rawlines = file.readlines() for line in rawlines: ############################### # Special case for feed tests # ############################### if(re.match(r"PASS \| xml", line) or re.match(r"FAIL \| xml", line)): try: self.lines.append(FeedTestLogLine(line)) except InvalidLogLineError: print "This line is not a feed test but shows up as such:\n%s" % line ############################# # Special case for reftests # ############################# elif(re.search(r"REFTEST", line)): try: self.lines.append(ReftestLogLine(line)) except InvalidLogLineError: print "This line is not a reftest but shows up as such:\n%s" % line ############################### # Standard test output format # ############################### elif(re.search(r"PASS", line) or re.search(r"FAIL", line) or re.search(r"EXPECTED RANDOM", line)): try: self.lines.append(TestLogLine(line)) except InvalidLogLineError: # TODO: figure out why this line isn't being executed ever print "This line is not a standard test but shows up as such:\n%s" % line ################################################################ # Make sure that the start time is captured for the feed tests # ################################################################ # This is a bad way of doing this but is there until timestamps # are added elif(re.match(r"Start: ", line)): time = line.replace("Start: ", "").strip() for x in self.lines: if(isinstance(x, FeedTestLogLine)): x.startTime = time def buildNumber(self): """I tell you which Build Number I am""" return 1 def startTime(self): """I tell you when I started""" return 1 def endTime(self): """I tell you when I finished""" return 10 def factory(self): """I tell you which buildbot Factory I came from""" return "f1" def builder(self): """I tell you which buildbot Builder I came from""" return "mozilla-buildbot" def slave(self): """I tell you which buildbot BuildSlave I came from""" return "local-osx" def testCount(self): """I tell you how many test's output I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): count = count + 1 return count def passCount(self): """I tell you how many test passes I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.passed()): count = count + 1 return count def failCount(self): """I tell you how many test fails I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.failed()): count = count + 1 return count def unexpectedCount(self): """I tell you how many unexpected test results I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.unexpected()): count = count + 1 return count def unexpectedPassCount(self): """I tell you how many unexpected passes I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.unexpectedPass()): count = count + 1 return count def unexpectedFailCount(self): """I tell you how many unexpected passes I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.unexpectedFail()): count = count + 1 return count def knownFailCount(self): """I tell you how many known fails I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.knownFail()): count = count + 1 return count def randomCount(self): """I tell you how many known fails I found""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.random()): count = count + 1 return count def badThingsCount(self): """I tell you how many BadThings happend""" count = 0 for line in self.lines: if isinstance(line, TestLogLine): if(line.unexpected() or line.fail()): count = count + 1 return count def logURL(self): """I tell you where you can find me""" return "http://www.example.com/waterfall" def logLines(self): """If you want my LogLines, ask me here""" return self.lines class LogLine: """I represent a Log Line. I might be more than one line of raw output from the log. I understand log output in the format "X | msg" where X is a time and msg is the rest of the output""" startTime = None # Time command starts msg = None # Message def __init__(self, line): chunks = line.split(" | ") self.startTime = chunks[0].strip() self.msg = chunks[1].strip() def time(self): """I Tell you the time it took to make this line""" return "%s minus %s" % startTime, endTime #def startTime(self): #"""I tell you the time this item was started""" #return startTime def endTime(self): """I tell you the time this line ended UNIMPLEMENTED""" return None class TestLogLine(LogLine): """I am a line of output specific to a Test run. I understand logs in the format "X | Y | Z | Q" Where X is a timestamp, Y is test status, Z is a test name and Q is the test message""" statusMsg = None # I store PASS, FAIL.... testName = None # I store the name of the test def __init__(self, line): """Parse a line into a TestLogLine output""" chunks = line.split(" | ") try: self.startTime = chunks[0].strip() self.statusMsg = chunks[1].strip() self.testName = chunks[2].strip() self.msg = chunks[3].strip() except IndexError: raise InvalidLogLineError("This line does not use the standard log output format") def passed(self): """I tell you if this line passed""" return re.search("PASS", self.statusMsg) != None def failed(self): """I tell you if this line failed""" return re.search("FAIL", self.statusMsg) != None def knownFail(self): """I tell you if this test was a known fail""" return re.search("KNOWN-FAIL", self.statusMsg) def unexpected(self): """I tell you if this was an unexpected result""" return re.search("UNEXPECTED", self.statusMsg) != None def unexpectedPass(self): """I tell you if this was an unexpected pass""" return self.passed() and self.unexpected() def unexpectedFail(self): """I tell you if this was an unexpected fail""" return self.failed() and self.unexpected() def random(self): """I tell you if this was expected random output""" return re.search(r"\(EXPECTED RANDOM\)", self.statusMsg) class FeedTestLogLine(TestLogLine): """I am a special case for the feed tests. I don't have timestamps in my output yet, so the Log's parsefile method adds the same timestamp to all of us. I understand log output in the form: X | Y | Z | Q where X is test status, Y and Z are the test name and Q is the test outcome""" def __init__(self, line): chunks = line.split(" | ") try: self.startTime = repr(0) self.statusMsg = chunks[0].strip() self.testName = "%s - %s" % (chunks[1].strip(), chunks[2].strip()) self.msg = chunks[3].strip() except IndexError: raise InvalidLogLineError("This line does not use the standard log output format") class ReftestLogLine(TestLogLine): """I am a line of Reftest output. I do not yet have timing information for each step and take a really long time to run so the suite's start time is not valid for me. I understand things in the format X | Y | Z where X is test status (with the word REFTEST removed), Y is the test's name and Z is any special messages""" def __init__(self, line): chunks = line.split(" | ") try: self.startTime = repr(0) self.statusMsg = chunks[0].replace("REFTEST ", "").strip() self.testName = chunks[1].strip() try: self.msg = chunks[2].strip() except IndexError: self.msg = "No Message" except IndexError: raise InvalidLogLineError("This Reftest does not use the standard log output format") class InvalidLogLineError(Exception): """I am an exception which is raised when someone makes an invalid LogLine""" pass if(__name__ == "__main__"): log = Log(argv[1]) # This is desired output format from Bug443329 # BUILDID/BUILDTIME | TESTTIME | TESTNAME | MACHINE(from this we can map to OS) | STATUS | MSG | link to full log for line in log.logLines(): if (isinstance(line, TestLogLine)): print "%s/%s | %s | %s | %s | %s | %s | %s" % ( log.buildNumber(), repr(log.startTime()), line.startTime, line.testName, log.slave(), line.statusMsg, line.msg, log.logURL()) print """Note: Build ID, Build Time, Machine Name, Link to log are all bogus fields""" """ This code is what i tested with for line in log.logLines(): if(isinstance(line, TestLogLine)): print "=" * 100 print "Line start: %s" % line.startTime print "Line status: %s" % line.statusMsg print "Line name: %s" % line.testName print "Line Msg: %s" % line.msg print "Count: %d" % log.testCount() print "Fails: %d" % log.failCount() print "Known Fails: %d" % log.knownFailCount() print "Passes: %d" % log.passCount() print "Unexpected: %d" % log.unexpectedCount() print "UnexpectedP: %d" % log.unexpectedPassCount() print "UnexpectedF: %d" % log.unexpectedFailCount() print "Random: %d" % log.randomCount()"""
Date Format Parser
This js code parses a terminal date string into a real date and time.
// This file is a parser for terminal "date" command // format strings. Likely, you will want to use only // timeParse(date, fmtstring) // Copyright 2009 John Ford // MPL-GPL-LGPL /* This function pads a signle digit to a leading 0 as a string */ function pad(number){ if(number <= 9){ return "0" + "" + number; } else { return number; } } /* Convert a specifer letter into a value based on date */ function specifier(date, letter){ switch(letter){ case 'Y': return "" + (1900 + date.getYear()); case 'm': //Whole numbering used for month return pad(date.getMonth() + 1); case 'd': return pad(date.getDate()); case 'H': return pad(date.getHours()); case 'M': return pad(date.getMinutes()); case 'S': return pad(date.getSeconds()); case 's': return date.valueOf(); default: return letter; } } /* This will parse date format strings to give valid output pased on POSIX date command when given a format and a date object. Returns string */ function timeParse(date, fmt){ var output =""; var input = "" + fmt; input = input.replace(/\+/, ""); input = input.replace(/%*/g, ""); for each (letter in input){ if (letter == '%'){ continue; } else { output += specifier(date, "" + letter); } } return output; }