def projectTestMake(PARENT_CASE): #create new FogBugzConnect object to talk to FBAPI fbConnection = FogBugzConnect() #get estimate print "Please provide an estimate for the test: ", est = raw_input() fbConnection.createTestCase(PARENT_CASE,estimate=est)
def ls(): gitConnection = GitConnect() (user,repo) = gitConnection.getUserRepo() fbConnection = FogBugzConnect() if repo=="DrewCrawfordApps": repo = "Hackity-Hack" elif repo=="Briefcase-wars": repo = "Briefcase Wars" fbConnection.listCases(repo)
def chargeback(case): import re fbConnection = FogBugzConnect() events = fbConnection.allEvents(case) total_time = 0 def parsecase(match): if match: (fromt,tot) = match.groups(0) import dateutil.parser fromd = dateutil.parser.parse(fromt) tod = dateutil.parser.parse(tot) return (tod - fromd).total_seconds() return 0 for event in events: match = re.match("recharge: A record was removed from this ticket: From (.*) to (.*)(?=ixPerson)",event) total_time += parsecase(match) match = re.match("recharge: A record was added to this ticket: From (.*) to (.*)(?=ixPerson)",event) total_time -= parsecase(match) total_time += fbConnection.getElapsed(case) * 60.0 * 60.0 (pcase,test) = fbConnection.getCaseTuple(case,oldTestCasesOK=True,exceptOnFailure=False) if test: total_time += fbConnection.getElapsed(test) * 60.0 * 60.0 juche.info(" %d hours" % (total_time / 60.0 / 60.0) ) return total_time / 60.0 / 60.0
def projectStart(CASE_NO, fromSpec): """ print "Case no: " + str(CASE_NO) print "from: " + fromSpec """ #create new gitConnect object to talk to git gitConnection = GitConnect() #check for unsaved changes to source code gitConnection.checkForUnsavedChanges() #create new FogBugzConnect object to talk to FBAPI fbConnection = FogBugzConnect() #check for FogBugz case and clock in fbConnection.startCase(CASE_NO) #checkout or create branch with CASE_NO gitConnection.checkoutBranch(CASE_NO,fromSpec,fbConnection) settings = get_setting_dict() if not "viewOnStart" in settings or settings["viewOnStart"] == 1: fbConnection.view(CASE_NO) juche.info("Use work ship to commit your changes")
def autoTestMake(CASE_NO,fbConnection=None): juche.info("autotestmake %d" % CASE_NO) if not fbConnection: fbConnection = FogBugzConnect() (implement,test) = fbConnection.getCaseTuple(CASE_NO,oldTestCasesOK=True,exceptOnFailure=False) if not test: ixTester = fbConnection.optimalIxTester(CASE_NO) fbConnection.createTestCase(CASE_NO,ixTester=ixTester) return True return False
def test_chargeback(self): f = FogBugzConnect() self.assertAlmostEqual(chargeback(1111),-5.123611111111112) #I'm not 100% sure that this test makes any sense #The sum of a set of tickets should be the same as the sum of the chargebacked tickets. #Note that chargeback adds up the test cases for us, but f.getElapsed does not self.assertAlmostEqual(chargeback(1997)+chargeback(2427)+chargeback(2431)+chargeback(2695),f.getElapsed(1997)+f.getElapsed(2427)+f.getElapsed(2431)+f.getElapsed(2695)+f.getElapsed(2186)+f.getElapsed(2521))
def __init__(self, dir=None): if not dir: dir = "%s/mockrepo/" % tempfile.gettempdir() if not os.path.exists(dir): os.makedirs(dir) self.dir = dir self.git = GitConnect(self.dir) self.fb = FogBugzConnect()
def workConfig(settingString): ALLOWED_SETTINGS = ["viewOnStart"] if len(settingString.split("=")) < 2: printUsageString() raise Exception("stacktraceplease") setting = settingString.split("=")[0] value = settingString.split("=")[1] if setting and value: fbConnection = FogBugzConnect() settings = fbConnection.getCredentials() if(not setting in ALLOWED_SETTINGS): juche.warn("setting not known. Will be added anyway.") fbConnection.setSetting(setting, value) else: printUsageString() raise Exception("stacktraceplease")
def projectStartTest(CASE_NO): gitConnection = GitConnect() gitConnection.checkForUnsavedChanges() fbConnection = FogBugzConnect() #get the appropriate cases out of FogBugz (parent,test) = fbConnection.getCaseTuple(CASE_NO) if not fbConnection.isReadyForTest(parent): print "This doesn't look ready to be tested (resolved/implemented)... are you sure about this? (press Enter to continue)" raw_input() gitConnection.fetch() gitConnection.checkoutBranch(parent,None,fbConnection) fbConnection.startCase(test,enforceNoTestCases=False) gitHubConnection = GitHubConnect() gitHubConnection.openPullRequestByName("work-%d" % CASE_NO)
def projectStop(): #create new gitConnect object to talk to git gitConnection = GitConnect() #check for unsaved changes to source code gitConnection.checkForUnsavedChanges() #create new FogBugzConnect object to talk to FBAPI fbConnection = FogBugzConnect() caseno = gitConnection.extractCaseFromBranch() #stop working on case and checkout master branch = gitConnection.getBranch() gitConnection.pushChangesToOriginBranch(branch) gitConnection.checkoutMaster() #clock out of project fbConnection.stopWork(caseno)
def projectFailTest(): gitConnection = GitConnect() gitConnection.checkForUnsavedChanges() reasons = {"0":"Failed a unit test.","1":"Failed a UI test"} for reason in reasons.keys(): print reason," ",reasons[reason] print "Or just type why it failed." reason = raw_input() if reason in reasons.keys(): reason = reasons[reason] purgatory = False else: purgatory = True caseno = gitConnection.extractCaseFromBranch() gitConnection.pushChangesToOriginBranch(gitConnection.getBranch()) gitConnection.checkoutMaster() fbConnection = FogBugzConnect() (parent,test) = fbConnection.getCaseTuple(caseno) if purgatory: fbConnection.commentOn(parent,PURGATORY_STMT) #this signals buildbot to fail the case back to the implementer after PURGATORY expires #buildbot special-cases Inspect passes to be in PURGATORY, so no signaling is required for the pass case fbConnection.fbConnection.assign(ixBug=parent,ixPersonAssignedTo=magic.BUILDBOT_IXPERSON,sEvent="Terribly sorry but your case FAILED a test: %s" % reason) fbConnection.stopWork(test) # play sounds! getstatusoutput ("afplay -v 7 %s/media/dundundun.aiff" % sys.prefix)
def _fixFors_to_EBS_dates(abbreviatedTest=False): juche.dictate(fixing_dates_per_ebs=1) fbConnection = FogBugzConnect() fixfors = fbConnection.dependencyOrder(fbConnection.listFixFors()) if abbreviatedTest: fixfors = fixfors[:5] for item in fixfors: name = fbConnection.nameForFixFor(fbConnection.fixForDetail(item)) if name.startswith("Never"): continue juche.info("processing %s %s" % (name,item)) date = fbConnection.getShipDate(item) from dateutil.parser import parse if not abbreviatedTest: fbConnection.editFixForShipDate(item,parse(date)) #It's bad to leave EBS in a partially-edited state. Therefore we simply log the output and don't actually write any changes when in abbreviated test mode. else: juche.warning("Not editing the ship date in %s to %s because this is an abbreviated test." % (item,date))
def projectIntegrate(CASE_NO,defaultgitConnection=GitConnect()): if not defaultgitConnection: interactive = True else: interactive = False gitConnection = defaultgitConnection gitConnection.checkForUnsavedChanges() #see referencing note on line 369 fbConnection = FogBugzConnect() #still open here # make sure integration is even worth it... fbConnection.ensureReadyForTest(CASE_NO) gitConnection.checkoutExistingBranch(CASE_NO) integrate_to = fbConnection.getIntegrationBranch(CASE_NO) gitConnection.checkoutExistingBranchRaw(integrate_to) gitConnection.resetHard_INCREDIBLY_DESTRUCTIVE_COMMAND() #this is safe because we check for unsafe changes on line 357. gitHubConnection = GitHubConnect(gitConnect=gitConnection) gitHubConnection.closePullRequestbyName("work-%d" % CASE_NO) #check for test case try: (parent, test) = fbConnection.getCaseTuple(CASE_NO,oldTestCasesOK=True) except: if interactive: juche.warn("no test case! Press enter to continue") raw_input() if not interactive: if not gitConnection.mergeIn("work-%d" % CASE_NO,pretend=True): return False gitConnection.mergeIn("work-%d" % CASE_NO) fbConnection.commentOn(CASE_NO,"Merged into %s" % integrate_to) fbConnection.closeCase(CASE_NO) if not interactive: gitConnection.pushChangesToOriginBranch(branch=integrate_to) return True
def complain(ixComplainAboutPerson,sComplainAboutProject): fbConnection = FogBugzConnect() response = fbConnection.fbConnection.search(q="status:active assignedto:=%d project:%s" % (ixComplainAboutPerson,sComplainAboutProject),cols="hrsCurrEst,hrsElapsed,sPersonAssignedTo,sFixFor,sCategory") for case in response.cases: #print case if case.hrscurrest.contents[0]=="0": juche.info("%s's case %s has no estimate" % (case.spersonassignedto.contents[0], case["ixbug"])) fbConnection.commentOn(case["ixbug"],"This next test could take a very, VERY long time.") if case.sfixfor.contents[0]=="Undecided" and (case.scategory.contents[0] == "bug" or case.scategory.contents[0] == "feature"): juche.info("%s needs a milestone" % case["ixbug"]) fbConnection.commentOn(case["ixbug"],"If you choose not to decide, you still have made a choice. (Don't think about it, don't think about it...) It's a paradox! There IS no answer.") est = float(case.hrscurrest.contents[0]) act = float(case.hrselapsed.contents[0]) if est - act < 0: juche.info("%s's case %s requires updated estimate" % (case.spersonassignedto.contents[0], case["ixbug"])) fbConnection.commentOn(case["ixbug"],"I'll give you credit: I guess you ARE listening to me. But for the record: You don't have to go THAT slowly.")
def _fixFors_test_quickly_dates(abbreviatedTest=False): juche.dictate(fixing_test_milestones=1) fbConnection = FogBugzConnect() fixfors_raw = fbConnection.listFixFors() fixfors = fbConnection.dependencyOrder(fixfors_raw) from dateutil.parser import parse import datetime if abbreviatedTest: fixfors = fixfors[:5] for testMilestone in fixfors: testMilestone_raw = fbConnection.fixForDetail(testMilestone) testName = fbConnection.nameForFixFor(testMilestone_raw) if testName.startswith("Never"): continue juche.dictate(testMilestone=testMilestone,testName=testName) if not testName.endswith("-test"): continue if testName=="Undecided-test": continue matched = False if testMilestone_raw.ixproject.contents==[]: continue for item in fbConnection.listFixFors(ixProject=int(testMilestone_raw.ixproject.contents[0])): #print testName[:-5],fbConnection.nameForFixFor(item) if item.sfixfor.contents[0]==testName[:-5]: #print "matching",testName,fbConnection.nameForFixFor(item) matched = True break if not matched: juche.info(testMilestone_raw) raise Exception("Cannot match "+testName) if item.dt.contents==[]: juche.info("Can't set %s because the non-test milestone has no completion date." % testName) continue date = item.dt.contents[0] newDate = parse(date)+datetime.timedelta(hours=6) #turns out that using 1 day produces weird results. If the next implementation milestone is completed within 24 hours, lots of weird things can happen juche.info("setting %s to %s"% (testName,newDate)) if not abbreviatedTest: fbConnection.editFixForShipDate(testMilestone,newDate) #It's bad to leave EBS in a partially-edited state. Therefore we simply log the output and don't actually write any changes when in abbreviated test mode. else: juche.warn("Not editing the ship date in %s to %s because this is an abbreviated test." % (testMilestone,newDate))
def projectPassTest(): gitConnection = GitConnect() gitConnection.checkForUnsavedChanges() caseno = gitConnection.extractCaseFromBranch() gitConnection.pushChangesToOriginBranch(gitConnection.getBranch()) gitConnection.checkoutMaster() fbConnection = FogBugzConnect() (parent,test) = fbConnection.getCaseTuple(caseno) statuses = fbConnection.getStatuses(test) for i in range(0,len(statuses.keys())): print i,": ",statuses[sorted(statuses.keys())[i]] print "Choose your adventure: ", choice = input() ix = sorted(statuses.keys())[choice] fbConnection.resolveCase(test,ixstatus=ix) fbConnection.closeCase(test) fbConnection.fbConnection.assign(ixBug=parent,ixPersonAssignedTo=magic.BUILDBOT_IXPERSON) # play sounds! getstatusoutput("afplay -v 7 %s/media/longcheer.aiff" % sys.prefix)
def releaseNotes(project,milestone): fb = FogBugzConnect() ixMilestone = fb.getIxFixFor(project,milestone) notes = fb.releaseNotes(ixMilestone) for note in notes: print note
def projectView(CASE_NO): fbConnection = FogBugzConnect() fbConnection.view(CASE_NO)
def recharge(fr,to): import dateutil.parser fbConnection = FogBugzConnect() fbConnection.setParentIfUnset(fr,to) #cannot create a time record for a closed case... mustOpen = not fbConnection.isOpen(to) if mustOpen: fbConnection.reopen(to,"work.py recharge") results = fbConnection.listTimeRecords(fr) time_interval = 0 my_records = [] for record in results: #print record if record.fdeleted.contents[0]!="false": juche.warn("Skipping deleted record %s" % record) continue if len(record.dtend)==0: juche.warn("Skipping open time record %s" % record) continue my_records.append(record) r = 0 for record in my_records: juche.info("%d: %s-%s" % (r,record.dtstart.contents[0],record.dtend.contents[0])) r += 1 def parse_range(astr): # http://stackoverflow.com/questions/4726168/parsing-command-line-input-for-numbers result=set() for part in astr.split(','): x=part.split('-') result.update(range(int(x[0]),int(x[-1])+1)) return sorted(result) strl = raw_input("records (syntax like 22-27,51-64): ") results = parse_range(strl) for result in results: record = my_records[result] record_desc = "From %s to %s ixPerson %s ixBug %s" % (record.dtstart.contents[0],record.dtend.contents[0],record.ixperson.contents[0],record.ixbug.contents[0]) from_time = dateutil.parser.parse(record.dtstart.contents[0]) to_time = dateutil.parser.parse(record.dtend.contents[0]) time_interval += (to_time-from_time).total_seconds() juche.info("from_time %s to_time %s time_interval %s" % (from_time,to_time,time_interval)) fbConnection.commentOn(fr,"recharge: A record was removed from this ticket: %s, see case %d" % (record_desc,to)) fbConnection.commentOn(to,"recharge: A record was added to this ticket: %s, see case %d" % (record_desc, fr)) fbConnection.createTimeRecord(to,str(record.dtstart.contents[0]),str(record.dtend.contents[0])) oldEst = fbConnection.getEstimate(fr) * 60.0 * 60.0 newEst = (oldEst - time_interval) / 60.0 / 60.0 if newEst <= 0: newEst = 1/60.0 juche.info("Setting estimate to",newEst) fbConnection.setEstimate(fr,timespan="%f hours" % newEst) #fbConnection.deleteTimeRecord(record.ixinterval.contents[0]) if mustOpen: fbConnection.closeCase(to)
def projectShip(): if lint_loaded: try: result = lint.Lint.analyze() except Exception as e: juche.exception(e) print "Lint sunk the ship, but we still have a liferaft!" else: if not result: while True: cont = raw_input("Your code failed lint analyses. Continue anyway? (Y/n)") if cont == "n": exit() if cont == "Y": break #create new gitConnect object to talk to git gitConnection = GitConnect() gitConnection.checkForUnsavedChanges(); #create new FogBugzConnect object to talk to FBAPI fbConnection = FogBugzConnect() #check if we're in a git repo branch = gitConnection.getBranch(); # check if branch is the right branch caseno = gitConnection.extractCaseFromBranch() gitConnection.pushChangesToOriginBranch(branch) gitConnection.checkoutMaster() #create the pull request gitHubConnect = GitHubConnect() if not gitHubConnect.pullRequestAlreadyExists("work-%d" % caseno): body = "Ticket at %s/default.asp?%d\n%s" % (fbConnection.getFBURL(),caseno,raw_input("Type a note: ")) list = gitConnection.getUserRepo() integrationBranch = fbConnection.getIntegrationBranch(caseno) if integrationBranch=="Undecided": raise Exception("Come on now, you've implemented the ticket before you decided what mielstone it was? You have to decide!") pullURL = gitHubConnect.createPullRequest("work-%d" % caseno,body,integrationBranch,"work-%d" % caseno) fbConnection.commentOn(caseno,"Pull request at %s\n%s" %(pullURL,body)) #is there a test case? try: (parent,child) = fbConnection.getCaseTuple(caseno) fbConnection.resolveCase(caseno,isTestCase_CASENO=child) except: fbConnection.resolveCase(caseno) pass print """There's about an 80% chance that whatever you just did was work that rightfully belongs to some other (possibly closed) case. Recharging is a way to signify to EBS that your work should be counted against a different case. Ex 1: You're fixing a somewhat-forseeable bug in a feature that was implemented and estimated in another case, but for some reason a new bug has been filed instead of the old feature reactivated. Recharge to the original feature, as whoever estimated that should have accounted for a relatively bug-free implementation. Ex 2: You're implementing a feature that was originally estimated in some other case. Maybe it was a parent case that was broken down into child cases, or maybe somebody carved out a feature of a larger something for you to implement. When there are multiple candidates for a recharge, use your judgment. Pick the newer case where reasonable. DO NOT RECHARGE 1) Things that are legitimately and substantially new features 2) Test cases, inquiries, or fake tickets 3) Build feedback / ship tickets """ print "recharge to: (case#)", TO_CASE = raw_input() if TO_CASE: to = int(TO_CASE) recharge(caseno,to)
class MockRepo: git = None dir = None fb = None def __init__(self, dir=None): if not dir: dir = "%s/mockrepo/" % tempfile.gettempdir() if not os.path.exists(dir): os.makedirs(dir) self.dir = dir self.git = GitConnect(self.dir) self.fb = FogBugzConnect() def createFile(self, name, contents): self.editFile(name, contents) def editFile(self, name, contents): with open(self.dir+name, "w") as file: file.write(contents) def readFile(self, name): with open(self.dir+name, "r") as file: return file.read() def gitInit(self): self.git.statusOutput("git init", self.dir) def gitAdd(self, file): self.git.add(file) def gitCommit(self, message): self.git.commitAll(message) def gitPush(self, forceful=False): if forceful: self.git.statusOutput("git push -u -f") else: self.git.statusOutput("git push -u") def gitPull(self): self.git.fetch() self.git.pull() self.git.submoduleUpdate() #Drew would like to document that this is redundant. Bion would like to document that this redundancy is not clear. def gitMerge(self, branch): self.git.mergeIn(branch) def gitCheckout(self, branch): self.git.checkoutExistingBranchRaw(branch) #is this name really necessary? def wipeRepo__INCREDIBLY__DESTRUCTIVE_COMMAND(self): self.git.statusOutputExcept("git push -f --all --delete") self.git.statusOutput("rm -Rf .git") self.git.statusOutputExcept("git init") self.git = GetConnect(self.dir) self.git.statusOutputExcept("git remote add origin [email protected]:drewcrawford/SampleProject.git") self.createFile("README.txt", "test") self.gitAdd("README.txt") self.gitCommit("First Commit") self.gitPush() def ticketReactivate(self, ticket): if "Closed" in self.fb.getStatuses(ticket): self.fb.reactivate(ticket, 7, "Just testing...") def ticketResolve(self, ticket): self.fb.resolveCase(ticket) def ticketClose(self, ticket): self.fb.closeCase(ticket) def ticketAssign(self, ticket, ixUser): self.fb.assignCase(ticket, ixUser) def ticketCreate(self, title): return self.fb.createCase(title, 1, 1, 7) def ticketSetEstimate(self, ticket, estimate): self.fb.setEstimate(ticket, estimate)