""" tt2p4.py Convert TestTrack Defects to Perforce jobs. This script takes defects which have a "changelists" custom field and creates P4 jobs, associating them with the changelists from TT. You must have the TestTrack ODBC driver installed for this to work. """ import os, string, time, sys import dbi, odbc import P4Client import P4Handler # TestTrack database must have a custom field called "Changelists" # Each TestTrack user must have a Notes entry with the first line reading: PerforceID: perforceusername # Put name of DSN here odbcDsn = "lumitest" # Put name of client used for fixes. fixClient = "lumigent-tt2p4.py" defaultPerforceUser = "stephen.ng" # in case we fail getting the user name p4Handler = None tt2P4Users = None users = {} def addJobs(cur, defectsChanged): row = cur.fetchone() while row: if defectsChanged.has_key(row[0]): jobname = "DEF%05d" % row[0] ttUserId = defectsChanged[row[0]] # The defectEditorUser is the person who inserted the "changelists" field. defectEditorUser = tt2P4Users[ttUserId] try: p4Handler.Run("job", "-d", jobname) except P4Handler.Error, err: # It's okay to delete non-existent job. if string.find(err.value, "doesn't exist") == -1: raise(err) # Add this defect if it has changelists associated if row[3] != None: jobSpec = "Job: " + jobname + "\n" jobSpec += "Status: open\n" jobSpec += "User: " + defectEditorUser + "\n" jobSpec += "FoundBy: " + string.strip(row[5]) + ", " + string.strip(row[4]) + "\n" jobSpec += "DefectNumber: %d\n" % row[0] jobSpec += "LongDescription:\n\t" jobSpec += string.replace(row[6], "\n", "\n\t") jobSpec += "\nDescription:\n" jobSpec += "\t" + string.strip(row[1]) p4Handler.inputData = jobSpec p4Handler.p4.User = defectEditorUser p4Handler.Run("job", "-i") changelists = string.splitfields(string.strip(row[3]), ",") jobStatus = "closed" for changelist in changelists: # Fixes are owned by the user who created the changelist. # Get the changelist try: changeinfo = p4Handler.Run("change", "-o", `int(changelist)`) changeinfo = string.split(changeinfo[0]["data"], "\n") except P4Handler.Error, err: if string.find(err.value, "unknown") != -1: # changelist doesn't exist, try next one. print "warning: " + err.value continue else: raise(err) # Parse changelist lines (too bad stat not supported). for line in changeinfo: if line[:5] == "User:": changeUser = string.split(line)[1] if line[:7] == "Status:": if string.split(line)[1] == "pending": jobStatus = "open" break # Validate the user name if users.has_key(changeUser): p4Handler.p4.User = changeUser else: p4Handler.p4.User = defectEditorUser # At last! Create the fix. try: p4Handler.Run("fix", "-s", jobStatus, "-c", `int(changelist)`, jobname) except P4Client.error, err: print "fix error" + `err` row = cur.fetchone() db = None lastTTChange = 0 def convertDefects(): global lastTTChange # Which defects have been changed? defectsChanged = {} cur = db.cursor() cur.execute("select IDDEFREC, NOTES, IDRECORD, IDUSER from DEFLOG where IDRECORD > %d" % lastTTChange) row = cur.fetchone() while row: defectNumber, changeNotes, ttChange, idUser = row if string.find(changeNotes, "Changelists") != -1: defectsChanged[defectNumber] = idUser if ttChange > lastTTChange: lastTTChange = ttChange row = cur.fetchone() cur.close() if len(defectsChanged) > 0: cur = db.cursor() cur.execute("select DEFECTNUM, SUMMARY, DATEMODIFY, CUSTVALUE, FIRSTNAME, LASTNAME, REPORTBY.DESCRPTN from CUSTMVAL, CUSTMDEF, DEFECTS, USERS, REPORTBY where CUSTMVAL.IDDEFREC=DEFECTS.IDRECORD and DEFECTS.IDENTERBY=USERS.IDRECORD and REPORTBY.IDDEFREC=DEFECTS.IDRECORD and CUSTMVAL.IDCUSTREC=CUSTMDEF.IDRECORD and CUSTMDEF.FIELDNAME='Changelists' ORDER BY DEFECTNUM") row = cur.fetchone() addJobs(cur, defectsChanged) cur.close() return `defectsChanged` return None def init(): import sys global db, p4Handler, users, tt2P4Users p4Handler = P4Handler.P4Handler() p4Handler.p4.Client = fixClient for u in p4Handler.Run("users"): users[u["User"]] = u["FullName"] db = odbc.odbc(odbcDsn) # Get mapping of user ids to Perforce names cur = db.cursor() cur.execute("select IDRECORD, NOTES FROM USERS") tt2P4Users = {} row = cur.fetchone() while row: try: tt2P4Users[int(row[0])] = string.split(row[1])[1] except: tt2P4Users[int(row[0])] = defaultPerforceUser # raise RuntimeError("Every user must have a PerforceID: username entry as the first line of Notes") row = cur.fetchone() cur.close() def teardown(): p4Handler.Final() def connect(): global db try: time.sleep(300) db = odbc.odbc(odbcDsn) return db except: print "Unexpected error:", sys.exc_info()[0] # print "DBI error: " + `err` return None def tryConnect(attempts): for i in range(attempts): if connect() != None: return time.sleep(300) raise RuntimeError("can't connect") if __name__=='__main__': init() while 1: try: convertDefects() time.sleep(1) print ".", except: print "Unexpected error:", sys.exc_info()[0] tryConnect(10)