Mozilla BuildBot Trending/Snippets

From CDOT Wiki
Revision as of 23:09, 22 February 2009 by John64 (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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;
}